前情提要
,这一篇是关于动态权限和动态目录的, shiro
的授权器
在碰到权限的校验时候才会去触发,这个时候就可以从数据库
中获取到用户关联的角色
, 角色绑定的权限,大概就如下图了
有兴趣可以了解一下RBAC
,大概就是如下的一个关系
动态目录
就更简单了,用户关联的角色,角色所拥有的目录,这个就是展示的目录了,修改数据库数据就可达到动态的目的。
正文开始
设计五个表,管理员表(也就是用户表,已存在)
、角色表
、目录表
、用户角色表
、角色目录表
,如果没懂这些关联,可以看一下图片,图片最下方标记了关系,希望能看懂 创建数据库
sys_menu
创建
sys_role
创建
sys_role_menu
创建
sys_user_role
记得创建对应的controller
和service
和dao
以及xml
、entity
太多了,就不一一创建展示了,记得service
和dao
集成mybatis plus
提供的类
修改上篇没动的授权器UserRealm
@Autowired
private SysMenuService menuService;
/**
* 授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("这里是授权");
UserEntity userEntity = (UserEntity) principalCollection.getPrimaryPrincipal();
Integer id = userEntity.getId();
//设置id为空则拥有所有权限,sql中设置了id为空则查询所有权限
List<SysMenuEntity> menuList = menuService.findByUserId(id);
//转存set是为了去重,保证权限唯一
Set<String> collect = menuList.stream().map(SysMenuEntity::getPerms).collect(Collectors.toSet());
//所有权限
Set<String> perms = new HashSet<>();
collect.stream().forEach(y -> {
//防止空的造成异常
if(!StringUtils.isEmpty(y)){
//存放无论是否有多个或者单个,直接变成数组,更加清晰
/**
* 查询的权限中含有sys:user:info,sys:user:list
* 存入的依旧是set,防止权限重复,直接切割分隔的权限
*/
perms.addAll(Arrays.asList(y.split(",")));
}
});
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//此处是放入权限中,不是role角色中
simpleAuthorizationInfo.setStringPermissions(perms);
return simpleAuthorizationInfo;
}
复制代码
SysMenuService
//用户查询关联的目录
List<SysMenuEntity> findByUserId(Integer userId);
复制代码
SysMenuServiceImpl
@Autowired
private SysMenuDao menuDao;
@Override
public List<SysMenuEntity> findByUserId(Integer userId) {
return menuDao.selectByUserId(userId);
}
复制代码
SysMenuDao
List<SysMenuEntity> selectByUserId(@Param("userId") Integer userId);
复制代码
SysMenuDao.xml
<select id="selectByUserId" resultType="com.macro.entity.SysMenuEntity">
select m.* from sys_user_role ur
LEFT JOIN sys_role_menu rm on rm.role_id = ur.role_id
LEFT JOIN sys_menu m on m.id = rm.menu_id
where 1=1
<if test="userId != null and userId != ''">
and ur.user_id = #{userId}
</if>
GROUP BY m.id
</select>
复制代码
开启注解,校验权限 ShiroConfig
/**
* @Title: authorizationAttributeSourceAdvisor
* @Description:开启shiro提供的权限相关的注解
* @param defaultWebSecurityManager
* @return AuthorizationAttributeSourceAdvisor
**/
@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
上面都关联起来了,然后退出,
重新登陆
,让他加载一下角色权限,试一下,没啥问题了
整合redis
pom.xml
添加redis
依赖
我这边单独引入spring-boot-starter-data-redis
会发生异常,所以增加commons-pool2
依赖 不信邪的可以试试
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 填redis坑-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
复制代码
#### 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<Session> 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 <K, V> Cache<K, V> getCache(String cacheName) throws CacheException {
System.out.println("缓存名称: "+cacheName);
return new RedisCache<K,V>(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<K,V> implements Cache<K,V> {
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<K> keys() {
return getRedisTemplate().opsForHash().keys(this.cacheName);
}
@Override
public Collection<V> values() {
return getRedisTemplate().opsForHash().values(this.cacheName);
}
private RedisTemplate getRedisTemplate(){
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtil.getBean("redisTemplate");
return redisTemplate;
}
}
复制代码
修改 ShiroConfig
//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());
复制代码
运行项目,登陆,
认证信息
第二条是session
修改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<SysMenuEntity> wrapper = new QueryWrapper<>();
wrapper.eq("is_del","0");
List<SysMenuEntity> 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
放入其中, 两个文件可以在网上搜索或者在我的源码中复制,源码在最下面
添加sysMenu.html
原本想使用layui
的redio
,无奈,和vue
有冲突故此使用Element UI
,但是使用会造成·Layui·去渲染element ui
的组件 所在我将目录
和菜单
以及按钮
加载都放在点击添加(add)
和点击修改(update)
时去加载列表
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/css/layui.css">
<script src="https://unpkg.com/[email protected]/dist/layui.js"></script>
<link rel="stylesheet" type="text/css" href="css/layui-admin.css"/>
<script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>
<script src="https://cdn.bootcss.com/vue/2.5.16/vue.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script src="https://unpkg.com/[email protected]/dist/axios.min.js"></script>
<script src="common.js"></script>
</head>
<body >
<div id="app">
<div class="layui-bg-gray panel" >
<div class="layui-card panel-height" v-show="show">
<div class="layui-card-body" >
<button type="button" class="layui-btn" @click="add">新增</button>
<table class="layui-table " id="table" lay-filter="table"></table>
</div>
</div>
<div class="layui-card panel-height" v-show="!show">
<div class="layui-card-header " >
{{title}}
</div>
<div class="layui-card-body">
<div class="layui-form" style="margin-top: 15px;" >
<div class="layui-form-item">
<label class="layui-form-label">类型 </label>
<div class="layui-input-block">
<el-radio @change="redioState" v-for="item in types" v-model="menu.type" :label="item.type">{{item.name}}</el-radio>
</div>
</div>
</div>
<div class="layui-form" >
<div class="layui-form-item">
<label class="layui-form-label">名称</label>
<div class="layui-input-inline">
<input type="text" required lay-verify="required" placeholder="名称" v-model="menu.name" autocomplete="off" class="layui-input">
</div>
</div>
</div>
<div class="layui-form" v-show="menu.type != 2">
<div class="layui-form-item">
<label class="layui-form-label">上级</label>
<div class="layui-input-inline">
<el-select v-model="menu.parentId" placeholder="请选择上级">
<el-option v-for="item in options" :key="item.id" :label="item.name" :value="item.id">
</el-option>
</el-select>
</div>
</div>
</div>
<div class="layui-form" v-show="menu.type == 1">
<div class="layui-form-item">
<label class="layui-form-label">路径</label>
<div class="layui-input-inline">
<input type="text" required lay-verify="required" placeholder="路径" v-model="menu.path" autocomplete="off" class="layui-input">
</div>
</div>
</div>
<div class="layui-form" v-show="menu.type == 0">
<div class="layui-form-item">
<label class="layui-form-label">权限</label>
<div class="layui-input-inline">
<input type="text" required lay-verify="required" placeholder="权限" v-model="menu.perms" autocomplete="off" class="layui-input">
</div>
</div>
</div>
<div class="layui-form" v-show="menu.type != 0">
<div class="layui-form-item">
<label class="layui-form-label">图标</label>
<div class="layui-input-inline">
<input type="text" required lay-verify="required" placeholder="图标" v-model="menu.icon" autocomplete="off" class="layui-input">
<code style="color: red">图标地址 <a href="http://www.shagua.wiki/project/3?p=85" style="color: blue">点击前往</a> </code>
</div>
</div>
</div>
<div class="layui-form" v-show="menu.type != 0" >
<div class="layui-form-item">
<label class="layui-form-label">排序</label>
<div class="layui-input-inline">
<input type="number" required lay-verify="required" placeholder="排序" v-model="menu.sort" autocomplete="off" class="layui-input">
</div>
</div>
</div>
<div slot="footer" style="margin-top: 50px;margin-left: 50px" >
<button type="button" class="layui-btn" @click="saveOrUpdate">确定</button>
<button type="button" class="layui-btn layui-btn-primary" @click="cannel">取消</button>
</div>
</div>
</div>
</div>
</div>
<script src="js/sysMenu.js"></script>
<script type="text/html" id="barDemo"></script>
</body>
</html>
复制代码
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: '<div> <span>{{d.type == 0?"按钮":d.sex==1?"目录":"菜单"}}</span> </div>'
}
, {field: 'icon', title: '图标',
templet: '<div><i class="layui-icon {{d.icon}}"></i></div>'
}
,{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
左侧目录栏添加一个菜单管理
<dl class="layui-nav-child" >
<dd><a href="./sysMenu.html">菜单管理</a></dd>
</dl>
复制代码
重启看一下效果,没毛病
目前上级没接口,不过js
已经写了
SysMenuController
新增typeList
接口
@GetMapping("type/{type}")
public Result typeList(@PathVariable Integer type){
QueryWrapper<SysMenuEntity> wrapper = new QueryWrapper<>();
wrapper.eq("type",type);
wrapper.eq("is_del","0");
List<SysMenuEntity> list = menuService.list(wrapper);
return Result.success(list);
}
复制代码
没什么问题
角色管理sysRole.html
这个模块东西很多
<!DOCTYPE html>
<html xmlns:shiro="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/css/layui.css">
<script src="https://unpkg.com/[email protected]/dist/layui.js"></script>
<link rel="stylesheet" type="text/css" href="css/layui-admin.css"/>
<script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>
<script src="https://cdn.bootcss.com/vue/2.5.16/vue.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script src="https://unpkg.com/[email protected]/dist/axios.min.js"></script>
<script src="common.js"></script>
</head>
<body >
<div id="app">
<div class="layui-bg-gray panel" >
<div class="layui-card panel-height" v-show="show">
<div class="layui-card-body" >
<button type="button" class="layui-btn" @click="add">新增</button>
<button type="button" class="layui-btn layui-btn-normal" @click="update">修改</button>
<button type="button" class="layui-btn layui-btn-danger" @click="del">删除</button>
<table class="layui-hide" id="table"></table>
</div>
</div>
<div class="layui-card panel-height" v-show="!show">
<div class="layui-card-header " >
{{title}}
</div>
<div class="layui-card-body">
<div class="layui-form" style="margin-top: 15px;" >
<div class="layui-form-item">
<label class="layui-form-label">角色名称</label>
<div class="layui-input-inline">
<input type="text" required lay-verify="required" placeholder="角色名称" v-model="role.roleName" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">关联权限</label>
<div class="layui-input-inline">
<el-tree
:data="treeData"
show-checkbox
ref="tree"
default-expand-all="true"
node-key="id"
:props="defaultProps">
</el-tree>
</div>
</div>
</div>
<div slot="footer" style="margin-top: 50px;margin-left: 50px" >
<button type="button" class="layui-btn" @click="saveOrUpdate">确定</button>
<button type="button" class="layui-btn layui-btn-primary" @click="cannel">取消</button>
</div>
</div>
</div>
</div>
</div>
<script src="js/sysRole.js"></script>
</body>
</html>
复制代码
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<SysRoleMenuEntity> 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 IService<SysRoleEntity> {
int updateEntity(SysRoleEntity role);
Result findList(PageEntity param);
int insert(SysRoleEntity role);
SysRoleEntity findById(Integer id);
void delIds(String[] ids);
}
复制代码
SysRoleServiceImpl
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 ServiceImpl<SysRoleDao, SysRoleEntity> implements 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<SysRoleMenuEntity> wrapper = new QueryWrapper<>();
wrapper.eq("role_id",role.getId());
//旧的所有关联菜单
List<SysRoleMenuEntity> list = roleMenuService.list(wrapper);
//新的关联菜单
List<SysRoleMenuEntity> menuList = role.getMenuList();
if(update > 0 && menuList != null && menuList.size() > 0){
//抽取旧的目录id
Set<Integer> arr = list.stream().map(SysRoleMenuEntity::getMenuId).collect(Collectors.toSet());
//新数据,包含父级
menuList =getMenuList(menuList);
//抽出所有的目录id,并且保证不重复
Set<Integer> 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<SysRoleEntity> wrapper = new QueryWrapper<>();
wrapper.eq("is_del","0");
List<SysRoleEntity> list = roleDao.selectList(wrapper);
PageInfo<SysRoleEntity> 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<SysRoleMenuEntity> 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<SysRoleMenuEntity> menuList = roleMenuService.findByRoleId(id);
entity.setMenuList(menuList);
}
return entity;
}
@Override
public void delIds(String[] ids) {
//删除角色
roleDao.deleteBatchIds(Arrays.asList(ids));
//删除关联的信息
roleMenuService.remove(new QueryWrapper<SysRoleMenuEntity>().in("role_id",ids));
}
//新增和修改共用,获取目录和菜单id
public List<SysRoleMenuEntity> getMenuList(List<SysRoleMenuEntity> list){
Set<Integer> parentIdList = list.stream().map(SysRoleMenuEntity::getParentId).collect(Collectors.toSet());
if(parentIdList !=null && parentIdList.size() >0){
QueryWrapper<SysMenuEntity> wrapper = new QueryWrapper();
//排除为父级为0的数据
wrapper.ne("parent_id","0");
wrapper.in("id",parentIdList);
//查出菜单和菜单,直接获取父节点的id
List<SysMenuEntity> parentList = menuService.list(wrapper);
Set<Integer> 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;
}
}
复制代码
SysRoleMenuService
增加findByRoleId
List<SysRoleMenuEntity> findByRoleId(Integer roleId);
复制代码
SysRoleMenuServiceImpl
实现findByRoleId
方法
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 ServiceImpl<SysRoleMenuDao, SysRoleMenuEntity> implements SysRoleMenuService {
@Autowired
private SysRoleMenuDao roleMenuDao;
@Override
public List<SysRoleMenuEntity> findByRoleId(Integer roleId) {
return roleMenuDao.findByRoleId(roleId);
}
}
复制代码
SysRoleMenuDao
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 BaseMapper<SysRoleMenuEntity> {
List<SysRoleMenuEntity> findByRoleId(@Param("roleId") Integer roleId);
}
复制代码
SysRoleMenuDao.xml
<?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.macro.dao.SysRoleMenuDao">
<select id="findByRoleId" resultType="com.macro.entity.SysRoleMenuEntity">
select m.id menuId,rm.role_id roleId,rm.id from sys_role_menu rm
LEFT JOIN sys_menu m on m.id = rm.menu_id
where rm.role_id = #{roleId} and type = 0
ORDER BY m.id
</select>
</mapper>
复制代码
index.html
增加角色管理(sysRole.html)
和上次增加菜单管理一样
<dl class="layui-nav-child" >
<dd><a href="./sysRole.html">角色管理</a></dd>
</dl>
复制代码
运行效果,此时还看不到菜单树 增加
menu/menuList
接口
SysMenuEntity
增加子集
//非表字段
@TableField(exist = false)
private List<SysMenuEntity> childList;
复制代码
SysMenuController
新增 menuList
/**
* 用于展示角色中展示
* @return
*/
@GetMapping("menuList")
public Result menuList(){
//用户展示树节点的
Map<String,List<SysMenuEntity>> map = new HashMap<>();
List<SysMenuEntity> menuList = menuService.menuList();
//用于比较是否选中的
QueryWrapper<SysMenuEntity> wrapper = new QueryWrapper<>();
wrapper.eq("is_del","0");
List<SysMenuEntity> list = menuService.list(wrapper);
map.put("menuList",menuList);
map.put("list",list);
return Result.success(map);
}
复制代码
SysMenuService
新增 menuList
List<SysMenuEntity> menuList();
复制代码
SysMenuServiceImpl
/**
* 代码共用,用于展示层级,目或者菜单详情以及角色列表中权限展示
* @param menuList
* @return
*/
public List<SysMenuEntity> getHierarchyList(List<SysMenuEntity> menuList){
//获取所有的目录
//type = 2 为目录
List<SysMenuEntity> pathList = menuList.stream().filter(y -> y.getType().equals(2)).collect(Collectors.toList());
//获取所有菜单
//type = 1 为菜单
List<SysMenuEntity> childList = menuList.stream().filter(y -> y.getType().equals(1)).collect(Collectors.toList());
childList.stream().forEach( y -> {
//直接从sys_menu表查询权限
List<SysMenuEntity> child = menuDao.selectList(new QueryWrapper<SysMenuEntity>().eq("parent_id",y.getId()).eq("is_del","0"));
y.setChildList(child);
});
pathList.stream().forEach( y-> {
//目录id与 菜单的父级id相同 则菜单是目录的子级
List<SysMenuEntity> child = childList.stream().filter(x -> x.getParentId().equals(y.getId())).collect(Collectors.toList());
y.setChildList(child);
});
return pathList;
}
复制代码
运行效果如下:
总结
这篇太长了,想着一篇把动态目录也做了,算了,拆到下一篇,权限那块稍微麻烦一点,新权限可能移除了一些权限,也添加了一些权限,绿线
是相同的,不修改动的,红线
是需要移除的,蓝线
是需要新增的
所以我把旧数据的menuId
和新数据的menuId
提取出来了,旧数据
在新数据
中查询这个menuId
是否存在,不存在则是需要移除的,新数据
在旧数据
中查询某一个menuId
是否存在,新的menuId
不存在就是需要新增的
源码
在公众号内发送后台
即可获取源码
和数据库