[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...
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 = # </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/") 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/") public Result info(@PathVariable Integer id){ SysMenuEntity entity = menuService.getById(id); return Result.success(entity); } @PostMapping("del/") 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/[email protected]/dist/css/layui.css"> <script src="https://unpkg.com/[email protected]/dist/layui.js"></script> <link rel="stylesheet" type="text/css" href="css/layui-admin.css"/> <script src="http://code.jquery.com/jquery-2.1.1.min.js"></script> <script src="https://cdn.bootcss.com/vue/2.5.16/vue.min.js"></script> <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"> <script src="https://unpkg.com/element-ui/lib/index.js"></script> <script src="https://unpkg.com/[email protected]/dist/axios.min.js"></script> <script src="common.js"></script> </head> <body > <div id="app"> <div > <div v-show="show"> <div > <button type="button" @click="add">newly added</button> <table id="table" lay-filter="table"></table> </div> </div> <div v-show="!show"> <div > {} </div> <div> <div style="margin-top: 15px;" > <div> <label>type </label> <div> <el-radio @change="redioState" v-for="item in types" v-model="menu.type" :label="item.type">{}</el-radio> </div> </div> </div> <div > <div> <label>name</label> <div> <input type="text" required lay-verify="required" placeholder="name" v-model="menu.name" autocomplete="off"> </div> </div> </div> <div v-show="menu.type != 2"> <div> <label>superior</label> <div> <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 v-show="menu.type == 1"> <div> <label>route</label> <div> <input type="text" required lay-verify="required" placeholder="route" v-model="menu.path" autocomplete="off"> </div> </div> </div> <div v-show="menu.type == 0"> <div> <label>jurisdiction</label> <div> <input type="text" required lay-verify="required" placeholder="jurisdiction" v-model="menu.perms" autocomplete="off"> </div> </div> </div> <div v-show="menu.type != 0"> <div> <label>Icon</label> <div> <input type="text" required lay-verify="required" placeholder="Icon" v-model="menu.icon" autocomplete="off"> <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 v-show="menu.type != 0" > <div> <label>sort</label> <div> <input type="number" required lay-verify="required" placeholder="sort" v-model="menu.sort" autocomplete="off"> </div> </div> </div> <div slot="footer" style="margin-top: 50px;margin-left: 50px" > <button type="button" @click="saveOrUpdate">determine</button> <button type="button" @click="cannel">cancel</button> </div> </div> </div> </div> </div> <script src="js/sysMenu.js"></script> <script type="text/html" id="barDemo"> <a lay-event="edit">edit</a> <a 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: 'type', title: 'name', templet: '<div> <span>{}</span> </div>' } , }"></i></div>' } , ]] , 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(); vm.types.push(); vm.types.push(); }, //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(); 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(); 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 > <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/") 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/[email protected]/dist/css/layui.css"> <script src="https://unpkg.com/[email protected]/dist/layui.js"></script> <link rel="stylesheet" type="text/css" href="css/layui-admin.css"/> <script src="http://code.jquery.com/jquery-2.1.1.min.js"></script> <script src="https://cdn.bootcss.com/vue/2.5.16/vue.min.js"></script> <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"> <script src="https://unpkg.com/element-ui/lib/index.js"></script> <script src="https://unpkg.com/[email protected]/dist/axios.min.js"></script> <script src="common.js"></script> </head> <body > <div id="app"> <div > <div v-show="show"> <div > <button type="button" @click="add">newly added</button> <button type="button" @click="update">modify</button> <button type="button" @click="del">delete</button> <table id="table"></table> </div> </div> <div v-show="!show"> <div > {} </div> <div> <div style="margin-top: 15px;" > <div> <label>Role name</label> <div> <input type="text" required lay-verify="required" placeholder="Role name" v-model="role.roleName" autocomplete="off"> </div> </div> <div> <label>Associated permissions</label> <div> <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" @click="saveOrUpdate">determine</button> <button type="button" @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: [[ , , ]] ,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(); 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(); 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/") 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 = # 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 > <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

9 October 2021, 03:57 | Views: 6750

Add new comment

For adding a comment, please log in
or create account

0 comments