阅读 39
【爆肝推荐】手摸手带你做后台管理项目(第四章)整合redis添加shiro权限

【爆肝推荐】手摸手带你做后台管理项目(第四章)整合redis添加shiro权限

前情提要

,这一篇是关于动态权限和动态目录的, shiro授权器在碰到权限的校验时候才会去触发,这个时候就可以从数据库中获取到用户关联的角色, 角色绑定的权限,大概就如下图了 在这里插入图片描述

有兴趣可以了解一下RBAC,大概就是如下的一个关系 在这里插入图片描述

动态目录就更简单了,用户关联的角色,角色所拥有的目录,这个就是展示的目录了,修改数据库数据就可达到动态的目的。

正文开始

设计五个表,管理员表(也就是用户表,已存在)角色表目录表用户角色表角色目录表,如果没懂这些关联,可以看一下图片,图片最下方标记了关系,希望能看懂 在这里插入图片描述 创建数据库sys_menu 在这里插入图片描述 创建sys_role 在这里插入图片描述 创建sys_role_menu 在这里插入图片描述 创建sys_user_role 在这里插入图片描述

记得创建对应的controllerservicedao以及xmlentity

太多了,就不一一创建展示了,记得servicedao集成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_rolesys_user_rolesys_menusys_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包下新建sessionredis

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_menu1-16menu_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

我这个版本不能添加rediochecked,要不然不能成树形,可能我前端功力不够浑厚造成的 在static下新建一个treetable文件夹,将treetable.jstreetable.css放入其中, 两个文件可以在网上搜索或者在我的源码中复制,源码在最下面 在这里插入图片描述

添加sysMenu.html

原本想使用layuiredio,无奈,和vue有冲突故此使用Element UI,但是使用会造成·Layui·去渲染element ui的组件 所在我将目录菜单以及按钮加载都放在点击添加(add)点击修改(update)时去加载列表

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <link rel="stylesheet" href="https://unpkg.com/layui@2.6.8/dist/css/layui.css">
    <script src="https://unpkg.com/layui@2.6.8/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/axios@0.21.1/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">
    <a class="layui-btn layui-btn-xs" lay-event="edit">编辑</a>
    <a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
</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/layui@2.6.8/dist/css/layui.css">
    <script src="https://unpkg.com/layui@2.6.8/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/axios@0.21.1/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不存在就是需要新增的

源码

在公众号内发送后台即可获取源码数据库

image.png

文章分类
后端
文章标签