这一篇是关于动态权限和动态目录的,
shiro的授权器在碰到权限的校验时候才会去触发,这个时候就可以从数据库中获取到用户关联的角色,
角色绑定的权限,大概就如下图了
有兴趣可以了解一下RBAC,大概就是如下的一个关系
动态目录就更简单了,用户关联的角色,角色所拥有的目录,这个就是展示的目录了,修改数据库数据就可达到动态的目的。
正文开始设计五个表,管理员表(也就是用户表,已存在) 、角色表、目录表、用户角色表、角色目录表,如果没懂这些关联,可以看一下图片,图片最下方标记了关系,希望能看懂
创建数据库sys_menu
创建sys_role
创建sys_role_menu
创建sys_user_role
太多了,就不一一创建展示了,记得service和dao集成mybatis plus提供的类
修改上篇没动的授权器UserRealm @Autowired
private SysMenuService menuService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("这里是授权");
UserEntity userEntity = (UserEntity) principalCollection.getPrimaryPrincipal();
Integer id = userEntity.getId();
//设置id为空则拥有所有权限,sql中设置了id为空则查询所有权限
List menuList = menuService.findByUserId(id);
//转存set是为了去重,保证权限唯一
Set collect = menuList.stream().map(SysMenuEntity::getPerms).collect(Collectors.toSet());
//所有权限
Set perms = new HashSet<>();
collect.stream().forEach(y -> {
//防止空的造成异常
if(!StringUtils.isEmpty(y)){
//存放无论是否有多个或者单个,直接变成数组,更加清晰
perms.addAll(Arrays.asList(y.split(",")));
}
});
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//此处是放入权限中,不是role角色中
simpleAuthorizationInfo.setStringPermissions(perms);
return simpleAuthorizationInfo;
}
SysMenuService
//用户查询关联的目录 ListSysMenuServiceImplfindByUserId(Integer userId);
@Autowired
private SysMenuDao menuDao;
@Override
public List findByUserId(Integer userId) {
return menuDao.selectByUserId(userId);
}
SysMenuDao
ListSysMenuDao.xmlselectByUserId(@Param("userId") Integer userId);
开启注解,校验权限 ShiroConfig
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager defaultWebSecurityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(defaultWebSecurityManager);
return authorizationAttributeSourceAdvisor;
}
sysUserController中加入权限校验
添加的权限Permissions,授权器添加的也是权限,并非角色
注解没注意看,结果加错了,一直找,没找到原因,头都大了,后来仔细研究了一下才看到写错了
@RequiresRoles:角色校验 (如 : user)
@RequiresPermissions:权限校验 (如 : sys:user:info)
//只添加了@RequiresPermissions("sys:user:info")
//添加的权限Permissions,授权器添加的也是权限,并非角色
@RequiresPermissions("sys:user:info")
@GetMapping("info/{id}")
public Result info(@PathVariable("id") Integer id){
UserEntity userEntity = userService.getById(id);
return Result.success(userEntity);
}
运行项目后准备修改一条数据,这个时候角色和权限都是空的
发生了异常,Subject does not have permission [sys:user:info]
然后再sys_role、sys_user_role、sys_menu、sys_role_menu,中个添加一条数据
sys_role sys_user_role sys_menu sys_role_menu
上面都关联起来了,然后退出,重新登陆,让他加载一下角色权限,试一下,没啥问题了
我这边单独引入spring-boot-starter-data-redis会发生异常,所以增加commons-pool2依赖
不信邪的可以试试
org.springframework.boot
spring-boot-starter-data-redis
org.apache.commons
commons-pool2
#### yml引入redis
redis:
database: 0
host: 127.0.0.1
port: 6379
password:
timeout: 3000
lettuce:
pool:
max-active: 8
max-wait: -1
max-idle: 8
min-idle: 0
在utils包下新建session、redis包
session包下新建RedisSessionDao类
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.eis.AbstractSessionDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import java.io.Serializable;
import java.util.Collection;
import java.util.concurrent.TimeUnit;
public class RedisSessionDao extends AbstractSessionDAO {
// Session超时时间,单位为毫秒
private long expireTime = 1200000;
@Autowired
private RedisTemplate redisTemplate;// Redis操作类,对这个使用不熟悉的,可以参考前面的博客
public RedisSessionDao() {
super();
}
public RedisSessionDao(long expireTime, RedisTemplate redisTemplate) {
super();
this.expireTime = expireTime;
this.redisTemplate = redisTemplate;
}
@Override // 更新session
public void update(Session session) throws UnknownSessionException {
if (session == null || session.getId() == null) {
return;
}
session.setTimeout(expireTime);
redisTemplate.opsForValue().set(session.getId(), session, expireTime, TimeUnit.MILLISECONDS);
}
@Override // 删除session
public void delete(Session session) {
if (null == session) {
return;
}
redisTemplate.opsForValue().getOperations().delete(session.getId());
}
@Override// 获取活跃的session,可以用来统计在线人数,如果要实现这个功能,可以在将session加入redis时指定一个session前缀,统计的时候则使用keys("session-prefix*")的方式来模糊查找redis中所有的session集合
public Collection getActiveSessions() {
return redisTemplate.keys("*");
}
@Override// 加入session
protected Serializable doCreate(Session session) {
Serializable sessionId = this.generateSessionId(session);
this.assignSessionId(session, sessionId);
redisTemplate.opsForValue().set(session.getId(), session, expireTime, TimeUnit.MILLISECONDS);
return sessionId;
}
@Override// 读取session
protected Session doReadSession(Serializable sessionId) {
if (sessionId == null) {
return null;
}
Session session = (Session) redisTemplate.opsForValue().get(sessionId);
return session;
}
public long getExpireTime() {
return expireTime;
}
public void setExpireTime(long expireTime) {
this.expireTime = expireTime;
}
public RedisTemplate getRedisTemplate() {
return redisTemplate;
}
public void setRedisTemplate(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
}
#### 在utils包下新建ApplicationContextUtil工具类
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class ApplicationContextUtil implements ApplicationContextAware {
public static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("初始化");
context = applicationContext;
}
public static Object getBean(String beanName){
return context.getBean(beanName);
}
}
在redis包下新建RedisCacheManager类和RedisCache类
RedisCacheManager
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
public class RedisCacheManager implements CacheManager {
@Override
public Cache getCache(String cacheName) throws CacheException {
System.out.println("缓存名称: "+cacheName);
return new RedisCache(cacheName);
}
}
RedisCache
重写了shiro内部的缓存方式,采用了redis缓存
import com.macro.utils.ApplicationContextUtil; import org.apache.shiro.cache.Cache; import org.apache.shiro.cache.CacheException; import org.springframework.data.redis.core.RedisTemplate; import java.util.Collection; import java.util.Set; public class RedisCache修改 ShiroConfigimplements Cache { private String cacheName; public RedisCache() { } public RedisCache(String cacheName) { this.cacheName = cacheName; } @Override public V get(K k) throws CacheException { System.out.println("获取缓存:"+ k); return (V) getRedisTemplate().opsForHash().get(this.cacheName,k.toString()); } @Override public V put(K k, V v) throws CacheException { System.out.println("设置缓存key: "+k+" value:"+v); getRedisTemplate().opsForHash().put(this.cacheName,k.toString(),v); return null; } @Override public V remove(K k) throws CacheException { return (V) getRedisTemplate().opsForHash().delete(this.cacheName,k.toString()); } @Override public void clear() throws CacheException { getRedisTemplate().delete(this.cacheName); } @Override public int size() { return getRedisTemplate().opsForHash().size(this.cacheName).intValue(); } @Override public Set keys() { return getRedisTemplate().opsForHash().keys(this.cacheName); } @Override public Collection values() { return getRedisTemplate().opsForHash().values(this.cacheName); } private RedisTemplate getRedisTemplate(){ RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtil.getBean("redisTemplate"); return redisTemplate; } }
//2.创建安全管理器
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//给安全管理器设置
defaultWebSecurityManager.setRealm(realm);
//新增
//配置自定义的session缓存
defaultWebSecurityManager.setSessionManager(configWebSessionManager());
//配置自定义缓存redis
defaultWebSecurityManager.setCacheManager(redisCacheManager());
return defaultWebSecurityManager;
}
//3.将自定义的Realm 设置为Bean ,注入到2中
@Bean
public Realm getRealm(){
UserRealm realm = new UserRealm();
// 设置密码匹配器
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
// 设置加密方式
credentialsMatcher.setHashAlgorithmName("MD5");
// 设置散列次数
credentialsMatcher.setHashIterations(1024);
realm.setCredentialsMatcher(credentialsMatcher);
//这一步开始就是新加的了
realm.setCacheManager(redisCacheManager());
// 开启全局缓存
realm.setCachingEnabled(true);
// 开启认证缓存并指定缓存名称
realm.setAuthenticationCachingEnabled(true);
realm.setAuthenticationCacheName("authenticationCache");
// 开启授权缓存并指定缓存名称
realm.setAuthorizationCachingEnabled(true);
realm.setAuthorizationCacheName("authorizationCache");
return realm;
}
//新增的redis管理器
@Bean
public RedisCacheManager redisCacheManager(){
return new RedisCacheManager();
}
//新增的redis缓存
@Bean
public RedisSessionDao redisSessionDAO() {
RedisSessionDao redisSessionDAO = new RedisSessionDao();
return redisSessionDAO;
}
//新增定期删除过期缓存
@Bean
public DefaultWebSessionManager configWebSessionManager(){
DefaultWebSessionManager manager = new DefaultWebSessionManager();
manager.setSessionDAO(redisSessionDAO());// 设置SessionDao
manager.setDeleteInvalidSessions(true);// 删除过期的session
manager.setGlobalSessionTimeout( redisSessionDAO().getExpireTime());// 设置全局session超时时间
manager.setSessionValidationSchedulerEnabled(true);// 是否定时检查session
return manager;
}
修改UserRealm中认证器doGetAuthenticationInfo的加密方式
加密方式改成自定义的,上篇可能没看清就放上去了,虽然没什么问题,但是集成了redis就出现问题了
盐值加密
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(),
ByteSource.Util.bytes("1234"), getName());
改成
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(),
new CustomerByteSource("1234"), getName());
运行项目,登陆,
修改user某一条数据时,触发了info接口上面的鉴权, 然后去查询权限,并且缓存权限,也就出现第三条,缓存信息
缓存信息可以看得到一些权限信息,都是二进制存储
其实我试过保存为字符串, 但是会有异常,同样的代码一会是二进制,一会是字符串,无奈只能缓存二进制,如果知道答案的小伙子,可以跟我说一下,我太菜了!
以上redis整合结束,也把数据缓存进去了,过期了会自动删除
动态目录用户登陆之后,初始化时候加载用户的角色关联到的目录菜单
数据库添加数据sys_menu,查询中有两组权限,shiro授权器那边提前做了分割,可以回头看一下
sys_role_menu, 1-16的menu_id都关联role_id=1
之前用户绑定了角色在sys_user_role
SysMenuController
import com.macro.entity.SysMenuEntity;
import com.macro.service.SysMenuService;
import com.macro.utils.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
//目录管理
@RestController
@RequestMapping("menu")
public class SysMenuController {
@Autowired
private SysMenuService menuService;
@GetMapping("list")
public Result list(){
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.eq("is_del","0");
List list = menuService.list(wrapper);
return Result.success(0,(long)list.size(),list);
}
@PostMapping("save")
public Result save(@RequestBody SysMenuEntity menu){
if(menu.getType().equals(2)){
menu.setParentId(0);
}
menu.setIsDel(0);
boolean save = menuService.save(menu);
return save ? Result.success():Result.error("添加失败");
}
@PostMapping("update")
public Result update(@RequestBody SysMenuEntity menu){
menu.setIsDel(0);
boolean update = menuService.updateById(menu);
return update? Result.success():Result.error("修改失败");
}
@GetMapping("info/{id}")
public Result info(@PathVariable Integer id){
SysMenuEntity entity = menuService.getById(id);
return Result.success(entity);
}
@PostMapping("del/{id}")
public Result update(@PathVariable Integer id){
if(id > 0){
boolean type = menuService.removeById(id);
return type? Result.success():Result.error("删除失败");
}
return Result.success();
}
}
树形表格用的是layui版的treetable
我这个版本不能添加redio和checked,要不然不能成树形,可能我前端功力不够浑厚造成的
在static下新建一个treetable文件夹,将treetable.js和treetable.css放入其中,
两个文件可以在网上搜索或者在我的源码中复制,源码在最下面
原本想使用layui的redio,无奈,和vue有冲突故此使用Element UI,但是使用会造成·Layui·去渲染element ui的组件
所在我将目录和菜单以及按钮加载都放在点击添加(add)和点击修改(update)时去加载列表
{{title}}
sysMenu.js
我将加载树表格的请求封装起来了,保存或者修改或者删除后调用init(),让树重新加载即可
var vm = new Vue({
el:"#app",
mounted(){
this.init();
},
data:{
menu:{
name:null,
parentId:null,
path:null,
type:2,
perms:null,
icon:null,
sort:null
},
show:true,
title:"新增角色",
treeData:[],
defaultProps: {
children: 'childList',
label: 'name'
},
types:[],
options:[]
},
methods:{
init(){
layui.config({
// base: '/js/'存放treeTable.js的文件夹
base: '/treetable/'
}).use([ 'treetable','table'], function () {
let treeTable = layui.treetable;
var table = layui.table;
treeTable.render({
elem: '#table'
, cellMinWidth: 80
,treeSpid: '0'
,icon_key:'id',
icon: {
open: 'layui-icon layui-icon-triangle-d',
close: 'layui-icon layui-icon-triangle-r',
left: 16,
}
,hide_class: 'layui-hide'
,primary_key:"id"
,parent_key:'parentId'
,treeColIndex: 0
, url: 'menu/list'
,isPidData: true
,treePidName:'parentId'
, page: false
,treeDefaultClose: true //是否默认折叠
,treelinkage: false //父级展开时是否自动展开所有子级
,is_click_icon: false,
is_checkbox: false
, cols: [[
{field: 'name', title: '名称'}
, {field: 'path', title: '路径'}
, {field: 'perms', title: '权限'}
, {
field: 'type', title: '名称',
templet: ' {{d.type == 0?"按钮":d.sex==1?"目录":"菜单"}} '
}
, {field: 'icon', title: '图标',
templet: ''
}
,{fixed: 'right', align: 'center',title:'操作', toolbar: '#barDemo', width:150}
]]
, page: true
});
//监听行工具事件
//tool(table):table是 id值,elem的值,还得加一个 lay-filter="table"才会生效
table.on('tool(table)', function(obj){
console.log(obj)
if(obj.event === 'del'){
console.log("id",obj.data.id);
vm.del(obj.data.id);
} else if(obj.event === 'edit'){
vm.update(obj.data.id);
}
});
})
},
redioState(){
vm.menu.parentId = null;
vm.menu.name = null;
vm.menu.path = null;
vm.menu.perms = null;
vm.menu.icon = null;
vm.menu.sort = null;
if(vm.menu.type != 2){
vm.treeList(vm.menu.type+1);
}
},
//加载上级菜单
treeList(type){
axios({
url:"menu/type/"+type,
method: "get"
}).then(res =>{
if(res.data.code == 200){
console.log("####",res.data.data);
vm.options = res.data.data;
}
});
},
//填layui和vue的坑
reloadTypeList(){
vm.types=[];
vm.types.push({type:2, name:'目录'});
vm.types.push({type:1, name:'菜单'});
vm.types.push({type:0, name:'按钮'});
},
//查询+重新加载数据
reload(){
vm.init();
vm.show = true;
},
getType(type){
console.log("type",type)
vm.menu.type = type;
},
add(){
vm.show = false;
//初始化
vm.menu = {
name:null,
parentId:null,
path:null,
type:2,
perms:null,
icon:null,
sort:null
};
vm.reloadTypeList();
vm.title= "新增角色";
},
update(id){
vm.show = false;
vm.menu = {};
vm.reloadTypeList();
vm.info(id);
vm.title= "修改角色";
},
del(id){
let that = this;
layer.open({
title: '删除'
,content: '是否删除数据',
btn:['确定','取消'],
yes: function(index, layero){
axios({
url:"menu/del/"+id,
method: "post",
headers:{
"Content-Type": "application/json"
}
}).then(res =>{
if(res.data.code == 200){
that.$message({message:"删除成功", type: 'success'});
vm.reload();
}else {
that.$message.error("删除失败");
}
});
layer.close(index)
}
});
},
//保存或者更新
saveOrUpdate(){
let state = vm.menu.id == null|| vm.menu.id == "";
let url = state ?"menu/save":"menu/update";
axios({
url:url,
method: "post",
headers:{
"Content-Type": "application/json"
},
data:JSON.stringify(vm.menu)
}).then(res =>{
if(res.data.code == 200){
this.$message({message: state?"添加成功":"修改成功", type: 'success'});
vm.reload();
}else{
this.$message.error(state?'新增失败':"修改失败");
}
});
},
cannel(){
vm.show = true;
},
//查询单条
info(id){
axios({
method:"get",
url: "menu/info/" + id
}).then(res =>{
if(res.data.code == 200){
vm.menu = res.data.data;
if(res.data.data.type != 2){
vm.treeList(res.data.data.type+1);
}
}
})
},
}
})
index.html
左侧目录栏添加一个菜单管理
重启看一下效果,没毛病
目前上级没接口,不过js已经写了
@GetMapping("type/{type}")
public Result typeList(@PathVariable Integer type){
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.eq("type",type);
wrapper.eq("is_del","0");
List list = menuService.list(wrapper);
return Result.success(list);
}
没什么问题
这个模块东西很多
{{title}}
sysRole.js
var vm = new Vue({
el:"#app",
mounted(){
layui.use('table', function(){
var table = layui.table;
table.render({
elem: '#table'
,cellMinWidth: 80
,url:'role/list'
,cols: [[
{type:'checkbox'}
,{field:'id', title: 'id' }
,{field:'roleName', title: '角色名称' }
]]
,page: true
});
});
this.menuList();
},
data:{
role:{
roleName:null,
menuList:[]
},
show:true,
title:"新增角色",
treeData:[],
treeList:[],
//子集节点名称
defaultProps: {
children: 'childList',
label: 'name'
}
},
methods:{
menuList(){
axios({
url: "menu/menuList",
methods: "get"
}).then(res => {
//新增或者修改时被格式化的树节点
vm.treeData = res.data.data.menuList;
//未被格式化的数据,用于比较和获取父节点
vm.treeList = res.data.data.list;
});
},
//查询+重新加载数据
reload(){
layui.use('table', function () {
var table = layui.table;
table.reload('table', {
url: 'role/list'
});
});
vm.show = true;
},
add(){
vm.show = false;
//初始化
vm.role = {
roleName:null,
menuList:[]
};
vm.title= "新增角色";
//设置选中节点为空
this.$refs.tree.setCheckedKeys([]);
},
update(){
let data = ids();
if(data == null || data.length == 0 || data.length > 1){
alert("请选择一条数据!");
return;
}
//设置选中节点为空
this.$refs.tree.setCheckedKeys([]);
vm.show = false;
vm.role = {};
vm.info(data[0]);
vm.title= "修改角色";
},
del(){
let that = this;
let data = ids();
if(data == null || data.length == 0){
alert("请选择!")
return;
}
layer.open({
title: '删除'
,content: '是否删除数据',
btn:['确定','取消'],
yes: function(index, layero){
axios({
url:"role/del",
method: "post",
headers:{
"Content-Type": "application/json"
},
data:JSON.stringify(data)
}).then(res =>{
if(res.data.code == 200){
that.$message({message:"删除成功", type: 'success'});
vm.reload();
}else {
that.$message.error("删除失败");
}
});
layer.close(index)
}
});
},
//保存或者更新
saveOrUpdate(){
//抽取选中节点数据,只有子节点
let menuList = vm.getTreeData();
vm.role.menuList=menuList;
console.log("menuList",menuList);
let state = vm.role.id == null|| vm.role.id == "";
let url = state ?"role/save":"role/update";
axios({
url:url,
method: "post",
headers:{
"Content-Type": "application/json"
},
data:JSON.stringify(vm.role)
}).then(res =>{
if(res.data.code == 200){
this.$message({message: state?"添加成功":"修改成功", type: 'success'});
vm.reload();
}else{
this.$message.error(state?'新增失败':"修改失败");
}
});
},
cannel(){
vm.show = true;
},
//查询单条
info(id){
axios({
method:"get",
url: "role/info/" + id
}).then(res =>{
if(res.data.code == 200){
vm.role = res.data.data;
let list = res.data.data.menuList;
let arr =[];
for (let i = 0; i < list.length; i++) {
arr.push(list[i].menuId);
}
//从数据库中获取被选中的节点
this.$refs.tree.setCheckedKeys(arr);
}
})
},
//获取选中的增删改查标签
getTreeData(){
let arr =[];
let nodes = this.$refs.tree.getCheckedNodes();
if(nodes != null && nodes.length > 0){
for (let i = 0; i < nodes.length; i++) {
let child = nodes[i].childList;
//为空的则是增删改查标签
if(child == null){
console.log("tree",nodes[i]);
for (let j = 0; j < vm.treeList.length; j++) {
//比较节点,并且获取父级节点
if(vm.treeList[j].id == nodes[i].id){
arr.push({"menuId":vm.treeList[j].id,"parentId":vm.treeList[j].parentId});
}
}
}
}
}
return arr;
}
}
});
SysRoleEntity添加非表字段
//用于接收和反显关联的菜单数据
@TableField(exist = false)
private List menuList;
SysRoleMenuEntity添加非表字段
//用户接收,角色关联的菜单的父级 @TableField(exist = false) private Integer parentId;SysRoleController
import com.macro.Vo.PageEntity;
import com.macro.entity.SysRoleEntity;
import com.macro.service.SysRoleService;
import com.macro.utils.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("role")
public class SysRoleController {
@Autowired
private SysRoleService roleService;
@GetMapping("list")
public Result list(PageEntity param){
Result result = roleService.findList(param);
return result;
}
@PostMapping("save")
public Result save(@RequestBody SysRoleEntity role){
int num = roleService.insert(role);
return num > 0?Result.success():Result.error("添加失败");
}
@PostMapping("update")
public Result update(@RequestBody SysRoleEntity role){
int num = roleService.updateEntity(role);
return num > 0?Result.success():Result.error("更新失败");
}
@GetMapping("info/{id}")
public Result info(@PathVariable Integer id){
SysRoleEntity role = roleService.findById(id);
return Result.success(role);
}
@PostMapping("del")
public Result update(@RequestBody String[] ids){
roleService.delIds(ids);
return Result.success();
}
}
SysRoleService
import com.baomidou.mybatisplus.extension.service.IService; import com.macro.Vo.PageEntity; import com.macro.entity.SysRoleEntity; import com.macro.utils.Result; public interface SysRoleService extends IServiceSysRoleServiceImpl{ int updateEntity(SysRoleEntity role); Result findList(PageEntity param); int insert(SysRoleEntity role); SysRoleEntity findById(Integer id); void delIds(String[] ids); }
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.macro.Vo.PageEntity; import com.macro.dao.SysRoleDao; import com.macro.entity.SysMenuEntity; import com.macro.entity.SysRoleEntity; import com.macro.entity.SysRoleMenuEntity; import com.macro.service.SysMenuService; import com.macro.service.SysRoleMenuService; import com.macro.service.SysRoleService; import com.macro.utils.Result; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.*; import java.util.stream.Collectors; @Service public class SysRoleServiceImpl extends ServiceImplSysRoleMenuService增加findByRoleIdimplements SysRoleService { @Autowired private SysRoleDao roleDao; @Autowired private SysRoleMenuService roleMenuService; @Autowired private SysMenuService menuService; @Override public int updateEntity(SysRoleEntity role) { int update = roleDao.updateById(role); QueryWrapper wrapper = new QueryWrapper<>(); wrapper.eq("role_id",role.getId()); //旧的所有关联菜单 List list = roleMenuService.list(wrapper); //新的关联菜单 List menuList = role.getMenuList(); if(update > 0 && menuList != null && menuList.size() > 0){ //抽取旧的目录id Set arr = list.stream().map(SysRoleMenuEntity::getMenuId).collect(Collectors.toSet()); //新数据,包含父级 menuList =getMenuList(menuList); //抽出所有的目录id,并且保证不重复 Set menuIds = menuList.stream().map(SysRoleMenuEntity::getMenuId).collect(Collectors.toSet()); //不存在则删除 list.stream().forEach(y -> { if(!menuIds.contains(y.getMenuId())){ roleMenuService.removeById(y.getId()); } }); //不存在则新增 menuList.stream().forEach(y -> { if(!arr.contains(y.getMenuId())){ y.setRoleId(role.getId()); roleMenuService.save(y); } }); } return update; } @Override public Result findList(PageEntity param) { PageHelper.startPage(param.getPage(), param.getLimit()); QueryWrapper wrapper = new QueryWrapper<>(); wrapper.eq("is_del","0"); List list = roleDao.selectList(wrapper); PageInfo pageInfo = new PageInfo<>(list); return Result.success(0,pageInfo.getTotal(),list); } @Override public int insert(SysRoleEntity role) { int num = roleDao.insert(role); //获取父级 if(num > 0){ List list = role.getMenuList(); if(list != null && list.size() > 0){ list =getMenuList(list); list.stream().forEach(y -> { y.setRoleId(role.getId()); roleMenuService.save(y); }); } } return num; } @Override public SysRoleEntity findById(Integer id) { SysRoleEntity entity = roleDao.selectById(id); if(entity != null){ List menuList = roleMenuService.findByRoleId(id); entity.setMenuList(menuList); } return entity; } @Override public void delIds(String[] ids) { //删除角色 roleDao.deleteBatchIds(Arrays.asList(ids)); //删除关联的信息 roleMenuService.remove(new QueryWrapper ().in("role_id",ids)); } //新增和修改共用,获取目录和菜单id public List getMenuList(List list){ Set parentIdList = list.stream().map(SysRoleMenuEntity::getParentId).collect(Collectors.toSet()); if(parentIdList !=null && parentIdList.size() >0){ QueryWrapper wrapper = new QueryWrapper(); //排除为父级为0的数据 wrapper.ne("parent_id","0"); wrapper.in("id",parentIdList); //查出菜单和菜单,直接获取父节点的id List parentList = menuService.list(wrapper); Set collect = parentList.stream().map(SysMenuEntity::getParentId).collect(Collectors.toSet()); parentIdList.addAll(collect); parentIdList.stream().forEach(y -> { SysRoleMenuEntity entity = new SysRoleMenuEntity(); entity.setMenuId(y); list.add(entity); }); } return list; } }
ListSysRoleMenuServiceImpl实现findByRoleId方法findByRoleId(Integer roleId);
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.macro.dao.SysRoleMenuDao; import com.macro.entity.SysRoleMenuEntity; import com.macro.service.SysRoleMenuService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class SysRoleMenuServiceImpl extends ServiceImplSysRoleMenuDaoimplements SysRoleMenuService { @Autowired private SysRoleMenuDao roleMenuDao; @Override public List findByRoleId(Integer roleId) { return roleMenuDao.findByRoleId(roleId); } }
import com.baomidou.mybatisplus.core.mapper.baseMapper; import com.macro.entity.SysRoleMenuEntity; import org.apache.ibatis.annotations.Param; import java.util.List; public interface SysRoleMenuDao extends baseMapperSysRoleMenuDao.xml{ List findByRoleId(@Param("roleId") Integer roleId); }
index.html增加角色管理(sysRole.html)
和上次增加菜单管理一样
运行效果,此时还看不到菜单树
增加menu/menuList接口
//非表字段
@TableField(exist = false)
private List childList;
SysMenuController 新增 menuList
@GetMapping("menuList")
public Result menuList(){
//用户展示树节点的
Map> map = new HashMap<>();
List menuList = menuService.menuList();
//用于比较是否选中的
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.eq("is_del","0");
List list = menuService.list(wrapper);
map.put("menuList",menuList);
map.put("list",list);
return Result.success(map);
}
SysMenuService 新增 menuList
ListSysMenuServiceImplmenuList();
public List getHierarchyList(List menuList){
//获取所有的目录
//type = 2 为目录
List pathList = menuList.stream().filter(y -> y.getType().equals(2)).collect(Collectors.toList());
//获取所有菜单
//type = 1 为菜单
List childList = menuList.stream().filter(y -> y.getType().equals(1)).collect(Collectors.toList());
childList.stream().forEach( y -> {
//直接从sys_menu表查询权限
List child = menuDao.selectList(new QueryWrapper().eq("parent_id",y.getId()).eq("is_del","0"));
y.setChildList(child);
});
pathList.stream().forEach( y-> {
//目录id与 菜单的父级id相同 则菜单是目录的子级
List child = childList.stream().filter(x -> x.getParentId().equals(y.getId())).collect(Collectors.toList());
y.setChildList(child);
});
return pathList;
}
运行效果如下:
这篇太长了,想着一篇把动态目录也做了,算了,拆到下一篇,权限那块稍微麻烦一点,新权限可能移除了一些权限,也添加了一些权限,绿线是相同的,不修改动的,红线是需要移除的,蓝线是需要新增的
所以我把旧数据的menuId和新数据的menuId提取出来了,旧数据在新数据中查询这个menuId是否存在,不存在则是需要移除的,新数据在旧数据中查询某一个menuId是否存在,新的menuId不存在就是需要新增的
源码在公众号内发送后台即可获取源码和数据库



