[recommendation for exploding liver] touch your hand and take you to the background management project (Chapter 4) integrate redis and add shiro permissions

previously on

, this article is about dynamic permissions and dynamic directories,
shiro's authorizer will trigger when it encounters permission verification. At this time, it can obtain the user's associated role from the database,
The permissions of role binding are roughly as shown in the figure below

If you are interested, you can learn about RBAC, which is probably one of the following relationships

The dynamic directory is even simpler. The user's associated roles and the directories owned by the roles are the displayed directories. The dynamic purpose can be achieved by modifying the database data.

Text start

Design five tables: administrator table (i.e. user table, which already exists), role table, directory table, user role table and role directory table. If you don't understand these associations, you can look at the picture. The relationship is marked at the bottom of the picture. I hope you can understand them

Create database sys_menu

Create sys_role

Create sys_role_menu

Create sys_user_role

Remember to create the corresponding controller, service, dao, xml and entity

If there are too many, we won't create and display them one by one. Remember that service and dao integrate the classes provided by mybatis plus

Modify the authorization UserRealm in the previous article

  @Autowired
    private SysMenuService menuService;


  /**
     * to grant authorization
     * @param principalCollection
     * @return
     */
    @Override
   	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("This is authorization");
        UserEntity userEntity = (UserEntity) principalCollection.getPrimaryPrincipal();
        Integer id = userEntity.getId();
        //If the id is set to be blank, you have all permissions. If the id is set to be blank in sql, you can query all permissions
        List<SysMenuEntity> menuList = menuService.findByUserId(id);
        //The purpose of transferring to set is to remove duplication and ensure unique permissions
        Set<String> collect = menuList.stream().map(SysMenuEntity::getPerms).collect(Collectors.toSet());
        //All permissions
        Set<String> perms = new HashSet<>();
        collect.stream().forEach(y -> {
            //Prevent empty from causing exceptions
            if(!StringUtils.isEmpty(y)){
                //Whether there are multiple or single storage, it can be directly turned into an array, which is clearer
                /**
                 * The query permission contains sys: user: info and sys: user: list
                 * set is still stored to prevent duplicate permissions, and separate permissions are directly cut
                 */
                perms.addAll(Arrays.asList(y.split(",")));
            }

        });
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        //This is a permission, not a role
        simpleAuthorizationInfo.setStringPermissions(perms);
        return simpleAuthorizationInfo;
    }

SysMenuService

	//The user queries the associated directory
 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>

Open annotation and verify permission ShiroConfig

 /**
     * @Title: authorizationAttributeSourceAdvisor
     * @Description:Open the comments related to the permissions provided by shiro
     * @param defaultWebSecurityManager
     * @return AuthorizationAttributeSourceAdvisor
     **/
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager defaultWebSecurityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(defaultWebSecurityManager);
        return authorizationAttributeSourceAdvisor;
    }

Add permission verification in sysUserController

The Permissions added by the authorizer are also Permissions, not roles
I didn't pay attention to the notes. As a result, I added them incorrectly. I kept looking for the reason. I didn't find the reason. My head was big. Later, I studied it carefully and saw that it was wrong
@RequiresRoles: role verification (e.g. user)
@RequiresPermissions: permission verification (for example: sys:user:info)

    //Only @ RequiresPermissions("sys:user:info") was added
    //The Permissions added by the authorizer are also Permissions, not roles
    @RequiresPermissions("sys:user:info")
    @GetMapping("info/{id}")
    public Result info(@PathVariable("id") Integer id){
        UserEntity userEntity = userService.getById(id);
        return Result.success(userEntity);
    }

After running the project, you are ready to modify a piece of data. At this time, both roles and permissions are empty

An exception occurred, Subject does not have permission [sys:user:info]

Then sys_role,sys_user_role,sys_menu,sys_role_menu to add a piece of data

sys_role

sys_user_role

sys_menu

sys_role_menu


All the above are related, then exit and log in again. Let him load the role permissions and try. There's no problem

Integrate redis

Add redis dependency to pom.xml

When I introduce spring boot starter data redis separately, exceptions will occur, so the commons-pool2 dependency is added
Those who don't believe in evil can try

		 <!-- Redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
         <!--        fill redis pit-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

####Introducing redis into yml

  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

Create a new session and redis package under utils package

Create a new RedisSessionDao class under the session package

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 timeout in milliseconds
    private long expireTime = 1200000;

    @Autowired
    private RedisTemplate redisTemplate;// Redis operation class. For those unfamiliar with this use, please refer to the previous blog

    public RedisSessionDao() {
        super();
    }

    public RedisSessionDao(long expireTime, RedisTemplate redisTemplate) {
        super();
        this.expireTime = expireTime;
        this.redisTemplate = redisTemplate;
    }

    @Override // Update 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 // Delete session
    public void delete(Session session) {
        if (null == session) {
            return;
        }
        redisTemplate.opsForValue().getOperations().delete(session.getId());
    }

    @Override// Get active sessions, which can be used to count the number of online people. If you want to realize this function, you can specify a session prefix when adding sessions to redis, and use keys ("session prefix *") to vaguely find all session combinations in redis
    public Collection<Session> getActiveSessions() {
        return redisTemplate.keys("*");
    }

    @Override// Join 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// Read 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;
    }

}

####Create a new ApplicationContextUtil tool class under utils package

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("initialization");
        context = applicationContext;
    }

    /**
     * Get the class instance according to the class name in the factory
     */
    public static Object getBean(String beanName){
        return context.getBean(beanName);
    }
}

Create a new RedisCacheManager class and RedisCache class under redis package

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("Cache name: "+cacheName);
        return new RedisCache<K,V>(cacheName);
    }
}

RedisCache
The internal cache mode of shiro is rewritten and redis cache is adopted

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("Get cache:"+ k);
        return (V) getRedisTemplate().opsForHash().get(this.cacheName,k.toString());
    }

    @Override
    public V put(K k, V v) throws CacheException {
        System.out.println("Set cache 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;
    }
}

Modify ShiroConfig

    //2. Create security manager
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        //Settings for Security Manager
        defaultWebSecurityManager.setRealm(realm);
        //newly added
        //Configure custom session cache
        defaultWebSecurityManager.setSessionManager(configWebSessionManager());
        //Configure custom cache redis
        defaultWebSecurityManager.setCacheManager(redisCacheManager());
        return defaultWebSecurityManager;
    }

	//3. Set the custom Realm as Bean and inject it into 2
    @Bean
    public Realm getRealm(){
        UserRealm realm = new UserRealm();
        // Set password matcher
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        // Set encryption method
        credentialsMatcher.setHashAlgorithmName("MD5");
        // Sets the number of hashes
        credentialsMatcher.setHashIterations(1024);
        realm.setCredentialsMatcher(credentialsMatcher);

		//This step is new at the beginning
        realm.setCacheManager(redisCacheManager());
        // Turn on global cache
        realm.setCachingEnabled(true);
        // Turn on the authentication cache and specify the cache name
        realm.setAuthenticationCachingEnabled(true);
        realm.setAuthenticationCacheName("authenticationCache");
        // Turn on the authorization cache and specify the cache name
        realm.setAuthorizationCachingEnabled(true);
        realm.setAuthorizationCacheName("authorizationCache");

        return realm;
    }

	//New redis Manager
    @Bean
    public RedisCacheManager redisCacheManager(){
        return new RedisCacheManager();
    }
	//New redis cache
    @Bean
    public RedisSessionDao redisSessionDAO() {
        RedisSessionDao redisSessionDAO = new RedisSessionDao();
        return redisSessionDAO;
    }
	//Add periodically delete expired cache
    @Bean
    public DefaultWebSessionManager configWebSessionManager(){
        DefaultWebSessionManager manager = new DefaultWebSessionManager();
        manager.setSessionDAO(redisSessionDAO());// Set SessionDao
        manager.setDeleteInvalidSessions(true);// Delete expired session s
        manager.setGlobalSessionTimeout( redisSessionDAO().getExpireTime());// Set global session timeout
        manager.setSessionValidationSchedulerEnabled(true);// Check the session regularly
        return manager;
    }

Modify the encryption method of authenticator doGetAuthenticationInfo in UserRealm

The encryption method is changed to user-defined, which may be put on without reading the previous article. Although there is no problem, there is a problem with the integration of redis

Salt value encryption

 SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,  user.getPassword(), 
 ByteSource.Util.bytes("1234"), getName());

Change to

SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(),
new CustomerByteSource("1234"), getName());

Run the project, log in,

Authentication information

The second is session

When modifying a piece of user data, the authentication on the info interface is triggered, and then the permission is queried and cached. The third item, cached information, appears

Cache information

You can see some permission information, which is binary storage

In fact, I've tried to save as a string, but there will be exceptions. The same code is binary and string, but I can only cache binary. If you know the answer, you can tell me, I'm too delicious!

After the above redis integration, the data is also cached, and it will be automatically deleted when it expires

Dynamic directory

After the user logs in, the directory menu associated with the user's role is loaded during initialization

Add data to database

sys_menu, there are two groups of permissions in the query. The shiro authorizer has split them in advance. You can look back

sys_role_menu, 1-16_ All IDs are associated with roles_ id=1

Previously, the user bound the role in sys_user_role

Write interface

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.*;

//Directory management
@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("Add failed");
    }

    @PostMapping("update")
    public Result update(@RequestBody SysMenuEntity menu){
        menu.setIsDel(0);
        boolean update = menuService.updateById(menu);
        return update?  Result.success():Result.error("Modification failed");
    }

    @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("Deletion failed");
        }
        return Result.success();
    }


}

The tree table uses the layui version of treetable

I can't add redio and checked in this version, otherwise it can't form a tree. Maybe my front-end skills are not strong enough
Create a new treetable folder under static, and put treetable.js and treetable.css into it,
The two files can be searched on the Internet or copied in my source code. The source code is at the bottom

Add sysMenu.html

redio, which originally wanted to use layui, had no choice but had a conflict with vue, so it used element ui, but using it would cause · layui · to render the components of element ui
I load the list when I click Add and 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">newly added</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">type </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">name</label>
                        <div class="layui-input-inline">
                            <input type="text"  required  lay-verify="required" placeholder="name" 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">superior</label>
                        <div class="layui-input-inline">
                            <el-select v-model="menu.parentId" placeholder="Please select superior">
                                <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">route</label>
                        <div class="layui-input-inline">
                            <input type="text"  required  lay-verify="required" placeholder="route" 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">jurisdiction</label>
                        <div class="layui-input-inline">
                            <input type="text"  required  lay-verify="required" placeholder="jurisdiction" 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">Icon</label>
                        <div class="layui-input-inline">
                            <input type="text"  required  lay-verify="required" placeholder="Icon" v-model="menu.icon" autocomplete="off" class="layui-input">
                            <code style="color: red">Icon address <a href="http://www.shagua.wiki/project/3? P = 85 "style =" color: Blue "> Click to go to < / a > < / code >
                        </div>
                    </div>
                </div>

                <div class="layui-form" v-show="menu.type != 0" >
                    <div class="layui-form-item">
                        <label class="layui-form-label">sort</label>
                        <div class="layui-input-inline">
                            <input type="number"  required  lay-verify="required" placeholder="sort" 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">determine</button>
                    <button type="button" class="layui-btn layui-btn-primary" @click="cannel">cancel</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">edit</a>
    <a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">delete</a>
</script>
</body>
</html>

sysMenu.js

I encapsulate the request to load the tree form, save or modify or delete it and then call init(), so that the tree can be reloaded.

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:"New role",
        treeData:[],
        defaultProps: {
            children: 'childList',
            label: 'name'
        },
        types:[],
        options:[]
    },

    methods:{

        init(){
            layui.config({
                //   base: '/js /' folder where treeTable.js is stored
                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	//Default collapse
                    ,treeLinkage: false		//Whether all children are automatically expanded when the parent is expanded
                    ,is_click_icon: false,
                    is_checkbox: false
                    , cols: [[
                        {field: 'name', title: 'name'}
                        , {field: 'path', title: 'route'}
                        , {field: 'perms', title: 'jurisdiction'}
                        , {
                            field: 'type', title: 'name',
                            templet: '<div> <span>{{d.type == 0?"Button":d.sex==1?"catalogue":"menu"}}</span> </div>'
                        }
                        , {field: 'icon', title: 'Icon',
                            templet: '<div><i class="layui-icon {{d.icon}}"></i></div>'

                        }
                        ,{fixed: 'right', align: 'center',title:'operation', toolbar: '#barDemo', width:150}
                    ]]
                    , page: true
                });
                //Listening for line tool events
                //tool(table):table is the id value and the value of elem. It will take effect only after adding a layer 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);
            }
        },
         //Load parent menu
        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;
                }
            });
        },
        //Pit filling with layui and vue
        reloadTypeList(){
            vm.types=[];
            vm.types.push({type:2,  name:'catalogue'});
            vm.types.push({type:1,  name:'menu'});
            vm.types.push({type:0,  name:'Button'});

        },
        //Query + reload data
        reload(){
            vm.init();
            vm.show = true;
        },
        getType(type){
            console.log("type",type)
            vm.menu.type = type;
        },
        add(){
            vm.show = false;
            //initialization
            vm.menu = {
                name:null,
                parentId:null,
                path:null,
                type:2,
                perms:null,
                icon:null,
                sort:null
            };
            vm.reloadTypeList();
            vm.title= "New role";
        },
        update(id){
            vm.show = false;
            vm.menu = {};
            vm.reloadTypeList();
            vm.info(id);
            vm.title= "Modify role";
        },
        del(id){
            let that = this;
            layer.open({
                title: 'delete'
                ,content: 'Delete data',
                btn:['determine','cancel'],
                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:"Delete succeeded", type: 'success'});
                            vm.reload();
                        }else {
                            that.$message.error("Deletion failed");
                        }
                    });
                    layer.close(index)
                }
            });
        },
        //Save or update
        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?"Added successfully":"Modified successfully", type: 'success'});
                    vm.reload();
                }else{
                    this.$message.error(state?'Failed to add':"Modification failed");
                }
            });

        },
        cannel(){
            vm.show = true;
        },
        //Query single
        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

Add a menu management in the left directory bar

<dl class="layui-nav-child" >
      <dd><a href="./sysMenu.html">Menu management</a></dd>
</dl>

Restart to see the effect, no problem

At present, the superior has no interface, but js has been written

SysMenuController adds a typeList interface

   @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);
    }

No problem

Role management sysRole.html

There are many things in this module

<!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">newly added</button>
                <button type="button"  class="layui-btn layui-btn-normal" @click="update">modify</button>
                <button type="button"  class="layui-btn layui-btn-danger" @click="del">delete</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">Role name</label>
                        <div class="layui-input-inline">
                            <input type="text"  required  lay-verify="required" placeholder="Role name" v-model="role.roleName" autocomplete="off" class="layui-input">
                        </div>
                    </div>
                    <div class="layui-form-item">
                        <label class="layui-form-label">Associated permissions</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">determine</button>
                    <button type="button" class="layui-btn layui-btn-primary" @click="cannel">cancel</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: 'Role name' }
                ]]
                ,page: true
            });
        });
        this.menuList();
    },
    data:{
        role:{
            roleName:null,
            menuList:[]
        },
        show:true,
        title:"New role",
        treeData:[],
        treeList:[],
        //Subset node name
        defaultProps: {
            children: 'childList',
            label: 'name'
        }
    },

    methods:{
        menuList(){
            axios({
                url: "menu/menuList",
                methods: "get"
            }).then(res => {
                //Tree node formatted when adding or modifying
                vm.treeData = res.data.data.menuList;
                //Unformatted data used to compare and obtain parent nodes
                vm.treeList = res.data.data.list;
            });
        },

        //Query + reload data
        reload(){
            layui.use('table', function () {
                var table = layui.table;
                table.reload('table', {
                    url: 'role/list'
                });
            });
            vm.show = true;
        },
        add(){
            vm.show = false;
            //initialization
            vm.role = {
                roleName:null,
                menuList:[]
            };
            vm.title= "New role";
            //Set the selected node to null
            this.$refs.tree.setCheckedKeys([]);
        },
        update(){
            let data = ids();
            if(data == null || data.length == 0 || data.length  > 1){
                alert("Please select a piece of data!");
                return;
            }
            //Set the selected node to null
            this.$refs.tree.setCheckedKeys([]);
            vm.show = false;
            vm.role = {};
            vm.info(data[0]);
            vm.title= "Modify role";
        },
        del(){
            let that = this;
            let data = ids();
            if(data == null || data.length == 0){
                alert("Please select!")
                return;
            }
            layer.open({
                title: 'delete'
                ,content: 'Delete data',
                btn:['determine','cancel'],
                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:"Delete succeeded", type: 'success'});
                            vm.reload();
                        }else {
                            that.$message.error("Deletion failed");
                        }
                    });
                    layer.close(index)
                }
            });



        },
        //Save or update
        saveOrUpdate(){
            //Extract the selected node data, only child nodes
            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?"Added successfully":"Modified successfully", type: 'success'});
                    vm.reload();
                }else{
                    this.$message.error(state?'Failed to add':"Modification failed");
                }
            });

        },
        cannel(){
            vm.show = true;
        },
        //Query single
        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);
                    }
                    //Get the selected node from the database
                    this.$refs.tree.setCheckedKeys(arr);
                }

            })
        },
        //Get the selected add delete modify query tag
        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 it is blank, the tag is added, deleted, modified and queried
                    if(child == null){
                        console.log("tree",nodes[i]);
                        for (let j = 0; j < vm.treeList.length; j++) {
                            //Compare nodes and get parent nodes
                            if(vm.treeList[j].id == nodes[i].id){
                                arr.push({"menuId":vm.treeList[j].id,"parentId":vm.treeList[j].parentId});
                            }
                        }
                    }
                }
            }
            return arr;
        }
    }
});

Adding non table fields to SysRoleEntity

//Used to receive and display the associated menu data
    @TableField(exist = false)
    private List<SysRoleMenuEntity> menuList;

Adding non table fields to sysrolemenentity

//The user receives the parent of the menu associated with the role
@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("Add failed");
    }

    @PostMapping("update")
    public Result update(@RequestBody SysRoleEntity role){
        int num = roleService.updateEntity(role);
        return num > 0?Result.success():Result.error("Update failed");
    }

    @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());
        //Old all context menus
        List<SysRoleMenuEntity> list = roleMenuService.list(wrapper);
        //New context menu
        List<SysRoleMenuEntity> menuList = role.getMenuList();
        if(update > 0 && menuList != null && menuList.size() > 0){
            //Extract old directory id
            Set<Integer> arr = list.stream().map(SysRoleMenuEntity::getMenuId).collect(Collectors.toSet());
            //New data, including parent
            menuList =getMenuList(menuList);
            //Extract all directory IDs and ensure that they are not repeated
            Set<Integer> menuIds = menuList.stream().map(SysRoleMenuEntity::getMenuId).collect(Collectors.toSet());
            //Delete if it does not exist
            list.stream().forEach(y -> {
                if(!menuIds.contains(y.getMenuId())){
                    roleMenuService.removeById(y.getId());
                }
            });
            //Add if it does not exist
            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);
        //Get parent
        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) {
        //Delete role
        roleDao.deleteBatchIds(Arrays.asList(ids));
        //Delete associated information
        roleMenuService.remove(new QueryWrapper<SysRoleMenuEntity>().in("role_id",ids));
    }


    //Add and modify common, and obtain the directory and menu 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();
            //Exclude data with parent 0
            wrapper.ne("parent_id","0");
            wrapper.in("id",parentIdList);

            //Find out the menu and menu, and directly obtain the id of the parent node
            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;
    }
}

Sysrolemenservice adds findByRoleId

 List<SysRoleMenuEntity> findByRoleId(Integer roleId);

Sysrolemenserviceimpl implements the findByRoleId method

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 add role management (sysRole.html)

It is the same as adding menu management last time

<dl class="layui-nav-child" >
   <dd><a href="./sysRole.html">Role management</a></dd>
</dl>

The menu tree cannot be seen at this time

Add menu/menuList interface

SysMenuEntity add subset

	//Non table field
    @TableField(exist = false)
    private List<SysMenuEntity> childList;

SysMenuController added menuList

   /**
     * Used to show in the role
     * @return
     */
    @GetMapping("menuList")
    public Result menuList(){
        //User presentation tree node
        Map<String,List<SysMenuEntity>> map = new HashMap<>();
        List<SysMenuEntity> menuList = menuService.menuList();

        //Used to compare whether selected
        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 added menuList

   List<SysMenuEntity> menuList();

SysMenuServiceImpl


        /**
     * Code sharing is used to display levels, item or menu details and permissions in the role list
     * @param menuList
     * @return
     */
    public List<SysMenuEntity> getHierarchyList(List<SysMenuEntity> menuList){
        //Get all directories
        //type = 2 is the directory
        List<SysMenuEntity> pathList = menuList.stream().filter(y -> y.getType().equals(2)).collect(Collectors.toList());
        //Get all menus
        //type = 1 is the menu
        List<SysMenuEntity> childList = menuList.stream().filter(y -> y.getType().equals(1)).collect(Collectors.toList());

        childList.stream().forEach( y -> {
            //Directly from sys_menu table query permission
            List<SysMenuEntity> child =  menuDao.selectList(new QueryWrapper<SysMenuEntity>().eq("parent_id",y.getId()).eq("is_del","0"));
            y.setChildList(child);
        });
        pathList.stream().forEach( y-> {
            //If the directory id is the same as the parent id of the menu, the menu is the child of the directory
            List<SysMenuEntity> child = childList.stream().filter(x -> x.getParentId().equals(y.getId())).collect(Collectors.toList());
            y.setChildList(child);
        });
        return pathList;
    }

The operation effect is as follows:

summary

This article is too long. I want to do the dynamic directory in the first article. Forget it. It's a little troublesome to remove the permissions in the next article. Some permissions may be removed and added in the new permissions. The green line is the same and does not change. The red line needs to be removed and the blue line needs to be added

Therefore, I extracted the menuId of the old data and the menuId of the new data. The old data queries whether the menuId exists in the new data. If it does not exist, it needs to be removed. The new data queries whether a menuId exists in the old data. If the new menuId does not exist, it needs to be added

Source code

Sending background to official account to get source code and database

Tags: Database Redis Shiro Spring Boot

Posted on Sat, 09 Oct 2021 03:57:52 -0400 by xtops