如何使用LayUI Shiro构建动态菜单并持久化菜单展开状态示例?

更新于
2026-09-26 12:53:39
1阅读来源:SEO资源
  • 内容介绍
  • 相关推荐

本文共计3418个文字,预计阅读时间需要14分钟。

如何使用LayUI Shiro构建动态菜单并持久化菜单展开状态示例?

目录

一、Maven

1. 依赖 2. 菜单相关的类 1. 主菜单 2. 子菜单

二、Shiro

1. 配置 1. ShiroConfig 2. 自定义Shiro密码加密校验

三、控制类

1. LoginController 2. PageController 3. MenuController

四、数据库

1. 前

目录
  • 一、Maven 依赖
  • 二、菜单相关的类
    • 1、主菜单
    • 2、子菜单
  • 三、Shiro 配置
    • 1、ShiroConfig
    • 2、自定义shiro密码校验
  • 四、控制类
    • 1、LoginController
    • 2、PageController
    • 3、MenuController
  • 五、数据库
    • 六、前端页面
      • 1、Ajax 请求菜单数据
      • 2、显示菜单栏
    • 七、完整代码

      LayUI + Shiro + Thyemleaf 实现动态菜单并记住菜单收展


      一、Maven 依赖

      <dependencies> <!--阿里 FastJson依赖--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.39</version> </dependency> <!--权限控制 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-starter</artifactId> <version>1.4.0-RC2</version> </dependency> <!-- 兼容于thymeleaf的shiro --> <dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.4</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>

      二、菜单相关的类

      1、主菜单

      /** * @author wxhntmy */ @Getter @Setter public class Menu { private String name; private String icon; private String url; private Boolean hidden; private List<MenuList> list; }

      2、子菜单

      /** * @author wxhntmy */ @Getter @Setter public class MenuList { private String name; private String url; public MenuList(String name, String url) { this.name = name; this.url = url; } }

      三、Shiro 配置

      如何使用LayUI Shiro构建动态菜单并持久化菜单展开状态示例?

      1、ShiroConfig

      /** * @author wxhntmy */ @Configuration public class ShiroConfig { /** * 配置拦截器 * <p> * 定义拦截URL权限,优先级从上到下 1). anon : 匿名访问,无需登录 2). authc : 登录后才能访问 3). logout: 登出 4). * roles : 角色过滤器 * <p> * URL 匹配风格 1). ?:匹配一个字符,如 /admin? 将匹配 /admin1,但不匹配 /admin 或 /admin/; 2). * *:匹配零个或多个字符串,如 /admin* 将匹配 /admin 或/admin123,但不匹配 /admin/1; 2). * **:匹配路径中的零个或多个路径,如 /admin/** 将匹配 /admin/a 或 /admin/a/b * <p> * 配置身份验证成功,失败的跳转路径 */ @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); // 静态资源匿名访问 filterChainDefinitionMap.put("/layui/**", "anon"); filterChainDefinitionMap.put("/js/**", "anon"); filterChainDefinitionMap.put("/admin/**", "anon"); filterChainDefinitionMap.put("/**/*.eot", "anon"); filterChainDefinitionMap.put("/**/*.svg", "anon"); filterChainDefinitionMap.put("/**/*.svgz", "anon"); filterChainDefinitionMap.put("/**/*.ttf", "anon"); filterChainDefinitionMap.put("/**/*.woff", "anon"); filterChainDefinitionMap.put("/**/*.woff2", "anon"); filterChainDefinitionMap.put("/**/*.gif", "anon"); filterChainDefinitionMap.put("/favicon.ico", "anon"); filterChainDefinitionMap.put("/login", "anon"); filterChainDefinitionMap.put("/menu", "anon"); filterChainDefinitionMap.put("/user/login", "anon"); // 用户退出 filterChainDefinitionMap.put("/logout", "logout"); // 其他路径均需要身份认证,一般位于最下面,优先级最低 filterChainDefinitionMap.put("/**", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); //登录路径 shiroFilterFactoryBean.setLoginUrl("/login"); // 主页 //shiroFilterFactoryBean.setSuccessUrl("/index"); //验证失败跳转的路径 shiroFilterFactoryBean.setUnauthorizedUrl("/error"); return shiroFilterFactoryBean; } /** * SecurityManager安全管理器;shiro的核心 * * @return */ @Bean public DefaultWebSecurityManager securityManager() { DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(myRealm()); return defaultWebSecurityManager; } /** * 自定义Realm * * @return */ @Bean public MyRealm myRealm() { MyRealm myRealm = new MyRealm(); myRealm.setCredentialsMatcher(myCredentialsMatcher()); return myRealm; } /** * 配置加密方式 * @return */ @Bean public MyCredentialsMatcher myCredentialsMatcher() { return new MyCredentialsMatcher(); } /** * 配置Shiro生命周期处理器 */ @Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } /** * 自动创建代理类,若不添加,Shiro的注解可能不会生效。 */ @Bean @DependsOn({ "lifecycleBeanPostProcessor" }) public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); advisorAutoProxyCreator.setProxyTargetClass(true); return advisorAutoProxyCreator; } /** * 开启Shiro的注解 */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager()); return authorizationAttributeSourceAdvisor; } @Bean public ShiroDialect shiroDialect() { return new ShiroDialect(); } }

      2、自定义shiro密码校验

      /** * 自定义shiro密码校验 * @author wxhntmy */ public class MyCredentialsMatcher implements CredentialsMatcher { @Resource private UserMapper userMapper; @Override public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { UsernamePasswordToken utoken = (UsernamePasswordToken) token; String password = new String(utoken.getPassword()); String username = utoken.getUsername(); User user = userMapper.getUserById(username); return user.getPwd().equals(password); } }

      3、MyRealm

      /** * @author wxhntmy */ public class MyRealm extends AuthorizingRealm { @Resource private RoleMapper roleMapper; @Resource private UserRoleListMapper userRoleListMapper; //授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); User user = (User) principalCollection.getPrimaryPrincipal(); if (user == null) { return null; } List<UserRoleList> roleLists = userRoleListMapper.getUserRoleByUserId(user.getId()); List<Role> roles = roleMapper.getAllRoles(); if (roleLists != null && !roleLists.isEmpty()) { for (UserRoleList roleList : roleLists) { for (Role role : roles) { if (Objects.equals(roleList.getRole_id(), role.getId())) { authorizationInfo.addRole(role.getRole()); } } } } return authorizationInfo; } //认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //获取登录用户账号 UsernamePasswordToken utoken = (UsernamePasswordToken) authenticationToken; //获得用户输入的密码 String password = new String(utoken.getPassword()); String username = utoken.getUsername(); User user = new User(); user.setId(username); user.setPwd(password); //当前realm对象的唯一名字,调用父类的getName()方法 String realmName = getName(); // 获取盐值,即用户名 ByteSource salt = ByteSource.Util.bytes(password); SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, salt, realmName); return info; } }

      四、控制类

      1、LoginController

      @RestController public class LoginController { @Resource private RoleMapper roleMapper; @Resource private UserRoleListMapper userRoleListMapper; @Resource private UserMapper userMapper; @RequestMapping(value = "/user/login", method = RequestMethod.GET) public Msg<String> getUserByName(@RequestParam String user, @RequestParam String pwd, @RequestParam String usertype, @RequestParam String box) { Role role = roleMapper.getRoleByRoleName(usertype); User uUser = userMapper.getUserById(user); if (uUser == null){ return Msg.fail("UserUnexit"); } //登录验证 UsernamePasswordToken token = new UsernamePasswordToken(user, pwd); Subject subject = SecurityUtils.getSubject(); try { subject.login(token); } catch (AuthenticationException e) { return Msg.fail("PasswordError"); } //设置登陆过期时间,单位毫秒,这里设置30分钟 SecurityUtils.getSubject().getSession().setTimeout(1800000); return Msg.ok("Success"); } }

      2、PageController

      @Controller public class PageController { @Resource private UserMapper userMapper; @RequestMapping(value = "/login", method = RequestMethod.GET) public String Login(){ return "login"; } @RequestMapping(value = "/user/index", method = RequestMethod.GET) public String Index(Model model){ User user = (User) SecurityUtils.getSubject().getPrincipal(); User uuser = userMapper.getUserById(user.getId()); if (StringUtils.isEmpty(user)) { return "redirect:/login"; } model.addAttribute("user", uuser); return "index"; } }

      3、MenuController

      /** * @author wxhntmy */ @RestController public class MenuController { @Resource private RoleMapper roleMapper; @Resource private UserRoleListMapper userRoleListMapper; //记住用户菜单收展 private Map<String, Map> menu_map = new HashMap<>(); @RequestMapping(value = "/menu", method = RequestMethod.GET) public Msg<List<Menu>> getMenu() { User user = (User) SecurityUtils.getSubject().getPrincipal(); List<Menu> list = new ArrayList<>(); if (StringUtils.isEmpty(user)) { return Msg.fail(list, "登录信息已过期!请重新登录!"); } //记住收展 Map<String, Boolean> map_store = new HashMap<>(); if (menu_map.containsKey(user.getId())){ map_store = menu_map.get(user.getId()); } Map<String, String> map = new HashMap(); List<UserRoleList> roleLists = userRoleListMapper.getUserRoleByUserId(user.getId()); for (UserRoleList roleList : roleLists) { Role role = roleMapper.getRoleByRoleId(roleList.getRole_id()); map.put(role.getRole(), roleList.getUser_id()); } Menu menu1 = new Menu(); menu1.setName("首页"); menu1.setIcon("&#xe68e;"); menu1.setUrl("/user/index"); menu1.setHidden(false); List<MenuList> menuLists2 = new ArrayList<>(); menu1.setList(menuLists2); list.add(menu1); if (map.containsKey("student")){ Menu menu2 = new Menu(); Menu menu3 = new Menu(); menu2.setName("课程管理"); menu2.setIcon("&#xe609;"); menu2.setUrl(""); menu2.setHidden(map_store.getOrDefault("课程管理", false)); List<MenuList> menuLists = new ArrayList<>(); MenuList menuList1 = new MenuList("已选课程", ""); MenuList menuList2 = new MenuList("可选课程", ""); MenuList menuList22 = new MenuList("课程论坛", ""); menuLists.add(menuList1); menuLists.add(menuList2); menuLists.add(menuList22); menu2.setList(menuLists); menu3.setName("成果管理"); menu3.setIcon("&#xe609;"); menu3.setUrl(""); menu3.setHidden(map_store.getOrDefault("成果管理", false)); List<MenuList> menuLists3 = new ArrayList<>(); MenuList menuList3 = new MenuList("提交课程成果", ""); MenuList menuList33 = new MenuList("提交课程日志", ""); MenuList menuList4 = new MenuList("实训课程成绩", ""); MenuList menuList5 = new MenuList("课程调查问卷", ""); menuLists3.add(menuList3); menuLists3.add(menuList33); menuLists3.add(menuList4); menuLists3.add(menuList5); menu3.setList(menuLists3); list.add(menu2); list.add(menu3); } if (map.containsKey("teacher")){ Menu menu2 = new Menu(); Menu menu3 = new Menu(); menu2.setName("授课课程管理"); menu2.setIcon("&#xe609;"); menu2.setUrl(""); menu2.setHidden(map_store.getOrDefault("授课课程管理", false)); List<MenuList> menuLists = new ArrayList<>(); MenuList menuList1 = new MenuList("教授的实训课程", ""); MenuList menuList2 = new MenuList("课程论坛", ""); menuLists.add(menuList1); menuLists.add(menuList2); menu2.setList(menuLists); menu3.setName("成果管理"); menu3.setIcon("&#xe609;"); menu3.setUrl(""); menu3.setHidden(map_store.getOrDefault("成果管理", false)); List<MenuList> menuLists3 = new ArrayList<>(); MenuList menuList3 = new MenuList("课程成果检查", ""); MenuList menuList33 = new MenuList("课程日志批复", ""); MenuList menuList4 = new MenuList("实训课程成绩", ""); menuLists3.add(menuList3); menuLists3.add(menuList33); menuLists3.add(menuList4); menu3.setList(menuLists3); list.add(menu2); list.add(menu3); } if (map.containsKey("professionor")){ Menu menu2 = new Menu(); Menu menu3 = new Menu(); menu2.setName("实训课程管理"); menu2.setIcon("&#xe609;"); menu2.setUrl(""); menu2.setHidden(map_store.getOrDefault("实训课程管理", false)); List<MenuList> menuLists = new ArrayList<>(); MenuList menuList1 = new MenuList("待批准实训课程", ""); MenuList menuList2 = new MenuList("添加实训课程", ""); MenuList menuList3 = new MenuList("实训课程管理", ""); menuLists.add(menuList1); menuLists.add(menuList2); menuLists.add(menuList3); menu2.setList(menuLists); menu3.setName("发布调查问卷"); menu3.setIcon("&#xe609;"); menu3.setUrl(""); menu3.setHidden(map_store.getOrDefault("发布调查问卷", false)); List<MenuList> menuLists1 = new ArrayList<>(); MenuList menuList11 = new MenuList("发布调查问卷", ""); MenuList menuList21 = new MenuList("回收调查问卷", ""); menuLists1.add(menuList11); menuLists1.add(menuList21); menu3.setList(menuLists1); list.add(menu2); list.add(menu3); } if (map.containsKey("admin")){ Menu menu2 = new Menu(); Menu menu3 = new Menu(); menu2.setName("用户管理"); menu2.setIcon("&#xe612;"); menu2.setUrl(""); menu2.setHidden(map_store.getOrDefault("用户管理", false)); List<MenuList> menuLists = new ArrayList<>(); MenuList menuList0 = new MenuList("添加用户", ""); MenuList menuList1 = new MenuList("学生账号", ""); MenuList menuList2 = new MenuList("教师账号", ""); MenuList menuList3 = new MenuList("实训负责人账号", ""); menuLists.add(menuList0); menuLists.add(menuList1); menuLists.add(menuList2); menuLists.add(menuList3); menu2.setList(menuLists); menu3.setName("数据库管理"); menu3.setIcon("&#xe857;"); menu3.setUrl(""); menu3.setHidden(map_store.getOrDefault("数据库管理", false)); List<MenuList> menuLists3 = new ArrayList<>(); MenuList menuList4 = new MenuList("备份数据库", ""); MenuList menuList5 = new MenuList("还原数据库", ""); menuLists3.add(menuList4); menuLists3.add(menuList5); menu3.setList(menuLists3); list.add(menu2); list.add(menu3); } Menu menu4 = new Menu(); menu4.setName("系统设置"); menu4.setIcon("&#xe620;"); menu4.setUrl(""); menu4.setHidden(map_store.getOrDefault("系统设置", false)); List<MenuList> menuLists4 = new ArrayList<>(); MenuList menuList5 = new MenuList("修改个人信息", ""); MenuList menuList6 = new MenuList("修改密码", ""); MenuList menuList7 = new MenuList("清除缓存", ""); menuLists4.add(menuList5); menuLists4.add(menuList6); menuLists4.add(menuList7); menu4.setList(menuLists4); Menu menu5 = new Menu(); menu5.setName("退出登录"); menu5.setIcon("&#xe65c;"); menu5.setUrl("/logout"); menu5.setHidden(false); List<MenuList> menuLists5 = new ArrayList<>(); menu5.setList(menuLists5); list.add(menu4); list.add(menu5); if (map.containsKey("student")){ return Msg.ok(list, "STU"); } String message = null; if (map.containsKey("teacher")){ message = "TEA"; } if (map.containsKey("professionor")){ message = "PRI"; } if (map.containsKey("admin")){ message = "ADM"; } return Msg.ok(list, message); } @RequestMapping(value = "/menu_storage", method = RequestMethod.GET) public Msg<String> menu_storage(@RequestParam String data) { JSONArray jsonArray = JSONArray.parseArray(data); User user = (User) SecurityUtils.getSubject().getPrincipal(); if (StringUtils.isEmpty(user)) { return Msg.fail("登录信息已过期!请重新登录!"); } //记住收展 Map<String, Boolean> map_store = new HashMap<>(); for (Object o : jsonArray) { JSONObject jsonObject = JSONObject.parseObject(o.toString()); map_store.put(jsonObject.getString("name"), Boolean.valueOf(jsonObject.getString("hidden"))); } menu_map.put(user.getId(), map_store); return Msg.ok(); } }

      五、数据库

      1、user 表

      2、role 表

      3、user_role_list 表

      六、前端页面

      1、Ajax 请求菜单数据

      let config = {}; function set_menu() { //ajax提交信息 $.ajax({ type: "get", async: false, url: "/menu",// 请求发送到LoginServlet处 dataType: 'json', success: function (msg) { if (msg.ok === true && msg.data) { config["name"] = msg.message; config["menu"] = msg.data; } if (msg.ok === false) { window.location.href = "/logout"; } if (!msg.data) { window.location.href = "/logout"; } }, error: function (msg) { // 请求失败时执行该函数 layer.alert('请求菜单数据失败!!!', function (index) { //do something layer.close(index); }); } }); } set_menu(); $(document).ready(function () { //删除 $(".del").click(function () { var url = $(this).attr("href"); var id = $(this).attr("data-id"); layer.confirm('你确定要删除么?', { btn: ['确定', '取消'] }, function () { $.get(url, function (data) { if (data.code === 1) { $(id).fadeOut(); layer.msg(data.msg, {icon: 1}); } else { layer.msg(data.msg, {icon: 2}); } }); }, function () { layer.msg("您取消了删除!"); }); return false; }); }) layui.use('form', function () { var form = layui.form, layer = layui.layer; }); var vue = new Vue({ el: '#app', data: { webname: config.name, menu: [], address: [] }, created: function () { this.menu = config.menu; this.thisActive(); this.thisAttr(); }, methods: { //记住收展 onActive: function (pid, id = false) { let data; if (id === false) { data = this.menu[pid]; if (data.url.length > 0) { this.menu.forEach((v, k) => { v.active = false; v.list.forEach((v2, k2) => { v2.active = false; }) }) data.active = true; } data.hidden = !data.hidden; } else { this.menu.forEach((v, k) => { v.active = false; v.list.forEach((v2, k2) => { v2.active = false; }) }) data = this.menu[pid].list[id]; } this.updateStorage(); if (data.url.length > 0) { if (data.target) { if (data.target === '_blank') { window.open(data.url); } else { window.location.href = data.url; } } else { window.location.href = data.url; } } }, //更新菜单缓存 updateStorage() { //sessionStorage.menu = JSON.stringify(this.menu); $.ajax({ type: "get", async: false, url: "/menu_storage",// 请求发送到LoginServlet处 data: { "data": JSON.stringify(this.menu) }, dataType: 'json', success: function (msg) { }, error: function (msg) { // 请求失败时执行该函数 var index = layer.load(); layer.close(index); layer.alert('请求菜单数据失败!!!', function (index) { //do something layer.close(index); }); } }); }, //菜单高亮 thisActive: function () { let pathname = window.location.pathname; let host = window.location.host; let pid = false; let id = false; this.menu.forEach((v, k) => { let url = v.url; if (url.length > 0) { if (url[0] !== '/' && url.substr(0, 4) !== 'http') { url = '/' + url; } } if (pathname === url) { pid = k; } v.list.forEach((v2, k2) => { let url = v2.url; if (url.length > 0) { if (url[0] !== '/' && url.substr(0, 4) !== 'http') { url = '/' + url; } } if (pathname === url) { pid = k; id = k2; } }) }) if (id !== false) { this.menu[pid].list[id].active = true; } else { if (pid !== false) { this.menu[pid].active = true; } } this.updateStorage(); }, //当前位置 thisAttr: function () { //当前位置 let address = [{ name: '首页', url: '/user/index' }]; this.menu.forEach((v, k) => { v.list.forEach((v2, k2) => { if (v2.active) { address.push({ name: v.name, url: 'javascript:;' }) address.push({ name: v2.name, url: v2.url, }) this.address = address; } }) }) } } })

      2、显示菜单栏

      <ul class="cl"> <!--顶级分类--> <li v-for="vo,index in menu" :class="{hidden:vo.hidden}"> <a href="javascript:;" rel="external nofollow" rel="external nofollow" :class="{active:vo.active}" @click="onActive(index)"> <i class="layui-icon" v-html="vo.icon"></i> <span v-text="vo.name"></span> <i class="layui-icon arrow" v-show="vo.url.length==0">&#xe61a;</i> <i v-show="vo.active" class="layui-icon active">&#xe623;</i> </a> <!--子级分类--> <div v-for="vo2,index2 in vo.list"> <a href="javascript:;" rel="external nofollow" rel="external nofollow" :class="{active:vo2.active}" @click="onActive(index,index2)" v-text="vo2.name"></a> <i v-show="vo2.active" class="layui-icon active">&#xe623;</i> </div> </li> </ul>

      七、完整代码

      完整代码转 Gitee:wxhntmy / SpringBootLayuiMenu

      到此这篇关于LayUI+Shiro实现动态菜单并记住菜单收展的示例的文章就介绍到这了,更多相关LayUI Shiro动态菜单内容请搜索自由互联以前的文章或继续浏览下面的相关文章希望大家以后多多支持自由互联!

      本文共计3418个文字,预计阅读时间需要14分钟。

      如何使用LayUI Shiro构建动态菜单并持久化菜单展开状态示例?

      目录

      一、Maven

      1. 依赖 2. 菜单相关的类 1. 主菜单 2. 子菜单

      二、Shiro

      1. 配置 1. ShiroConfig 2. 自定义Shiro密码加密校验

      三、控制类

      1. LoginController 2. PageController 3. MenuController

      四、数据库

      1. 前

      目录
      • 一、Maven 依赖
      • 二、菜单相关的类
        • 1、主菜单
        • 2、子菜单
      • 三、Shiro 配置
        • 1、ShiroConfig
        • 2、自定义shiro密码校验
      • 四、控制类
        • 1、LoginController
        • 2、PageController
        • 3、MenuController
      • 五、数据库
        • 六、前端页面
          • 1、Ajax 请求菜单数据
          • 2、显示菜单栏
        • 七、完整代码

          LayUI + Shiro + Thyemleaf 实现动态菜单并记住菜单收展


          一、Maven 依赖

          <dependencies> <!--阿里 FastJson依赖--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.39</version> </dependency> <!--权限控制 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-starter</artifactId> <version>1.4.0-RC2</version> </dependency> <!-- 兼容于thymeleaf的shiro --> <dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.4</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>

          二、菜单相关的类

          1、主菜单

          /** * @author wxhntmy */ @Getter @Setter public class Menu { private String name; private String icon; private String url; private Boolean hidden; private List<MenuList> list; }

          2、子菜单

          /** * @author wxhntmy */ @Getter @Setter public class MenuList { private String name; private String url; public MenuList(String name, String url) { this.name = name; this.url = url; } }

          三、Shiro 配置

          如何使用LayUI Shiro构建动态菜单并持久化菜单展开状态示例?

          1、ShiroConfig

          /** * @author wxhntmy */ @Configuration public class ShiroConfig { /** * 配置拦截器 * <p> * 定义拦截URL权限,优先级从上到下 1). anon : 匿名访问,无需登录 2). authc : 登录后才能访问 3). logout: 登出 4). * roles : 角色过滤器 * <p> * URL 匹配风格 1). ?:匹配一个字符,如 /admin? 将匹配 /admin1,但不匹配 /admin 或 /admin/; 2). * *:匹配零个或多个字符串,如 /admin* 将匹配 /admin 或/admin123,但不匹配 /admin/1; 2). * **:匹配路径中的零个或多个路径,如 /admin/** 将匹配 /admin/a 或 /admin/a/b * <p> * 配置身份验证成功,失败的跳转路径 */ @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); // 静态资源匿名访问 filterChainDefinitionMap.put("/layui/**", "anon"); filterChainDefinitionMap.put("/js/**", "anon"); filterChainDefinitionMap.put("/admin/**", "anon"); filterChainDefinitionMap.put("/**/*.eot", "anon"); filterChainDefinitionMap.put("/**/*.svg", "anon"); filterChainDefinitionMap.put("/**/*.svgz", "anon"); filterChainDefinitionMap.put("/**/*.ttf", "anon"); filterChainDefinitionMap.put("/**/*.woff", "anon"); filterChainDefinitionMap.put("/**/*.woff2", "anon"); filterChainDefinitionMap.put("/**/*.gif", "anon"); filterChainDefinitionMap.put("/favicon.ico", "anon"); filterChainDefinitionMap.put("/login", "anon"); filterChainDefinitionMap.put("/menu", "anon"); filterChainDefinitionMap.put("/user/login", "anon"); // 用户退出 filterChainDefinitionMap.put("/logout", "logout"); // 其他路径均需要身份认证,一般位于最下面,优先级最低 filterChainDefinitionMap.put("/**", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); //登录路径 shiroFilterFactoryBean.setLoginUrl("/login"); // 主页 //shiroFilterFactoryBean.setSuccessUrl("/index"); //验证失败跳转的路径 shiroFilterFactoryBean.setUnauthorizedUrl("/error"); return shiroFilterFactoryBean; } /** * SecurityManager安全管理器;shiro的核心 * * @return */ @Bean public DefaultWebSecurityManager securityManager() { DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(myRealm()); return defaultWebSecurityManager; } /** * 自定义Realm * * @return */ @Bean public MyRealm myRealm() { MyRealm myRealm = new MyRealm(); myRealm.setCredentialsMatcher(myCredentialsMatcher()); return myRealm; } /** * 配置加密方式 * @return */ @Bean public MyCredentialsMatcher myCredentialsMatcher() { return new MyCredentialsMatcher(); } /** * 配置Shiro生命周期处理器 */ @Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } /** * 自动创建代理类,若不添加,Shiro的注解可能不会生效。 */ @Bean @DependsOn({ "lifecycleBeanPostProcessor" }) public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); advisorAutoProxyCreator.setProxyTargetClass(true); return advisorAutoProxyCreator; } /** * 开启Shiro的注解 */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager()); return authorizationAttributeSourceAdvisor; } @Bean public ShiroDialect shiroDialect() { return new ShiroDialect(); } }

          2、自定义shiro密码校验

          /** * 自定义shiro密码校验 * @author wxhntmy */ public class MyCredentialsMatcher implements CredentialsMatcher { @Resource private UserMapper userMapper; @Override public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { UsernamePasswordToken utoken = (UsernamePasswordToken) token; String password = new String(utoken.getPassword()); String username = utoken.getUsername(); User user = userMapper.getUserById(username); return user.getPwd().equals(password); } }

          3、MyRealm

          /** * @author wxhntmy */ public class MyRealm extends AuthorizingRealm { @Resource private RoleMapper roleMapper; @Resource private UserRoleListMapper userRoleListMapper; //授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); User user = (User) principalCollection.getPrimaryPrincipal(); if (user == null) { return null; } List<UserRoleList> roleLists = userRoleListMapper.getUserRoleByUserId(user.getId()); List<Role> roles = roleMapper.getAllRoles(); if (roleLists != null && !roleLists.isEmpty()) { for (UserRoleList roleList : roleLists) { for (Role role : roles) { if (Objects.equals(roleList.getRole_id(), role.getId())) { authorizationInfo.addRole(role.getRole()); } } } } return authorizationInfo; } //认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //获取登录用户账号 UsernamePasswordToken utoken = (UsernamePasswordToken) authenticationToken; //获得用户输入的密码 String password = new String(utoken.getPassword()); String username = utoken.getUsername(); User user = new User(); user.setId(username); user.setPwd(password); //当前realm对象的唯一名字,调用父类的getName()方法 String realmName = getName(); // 获取盐值,即用户名 ByteSource salt = ByteSource.Util.bytes(password); SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, salt, realmName); return info; } }

          四、控制类

          1、LoginController

          @RestController public class LoginController { @Resource private RoleMapper roleMapper; @Resource private UserRoleListMapper userRoleListMapper; @Resource private UserMapper userMapper; @RequestMapping(value = "/user/login", method = RequestMethod.GET) public Msg<String> getUserByName(@RequestParam String user, @RequestParam String pwd, @RequestParam String usertype, @RequestParam String box) { Role role = roleMapper.getRoleByRoleName(usertype); User uUser = userMapper.getUserById(user); if (uUser == null){ return Msg.fail("UserUnexit"); } //登录验证 UsernamePasswordToken token = new UsernamePasswordToken(user, pwd); Subject subject = SecurityUtils.getSubject(); try { subject.login(token); } catch (AuthenticationException e) { return Msg.fail("PasswordError"); } //设置登陆过期时间,单位毫秒,这里设置30分钟 SecurityUtils.getSubject().getSession().setTimeout(1800000); return Msg.ok("Success"); } }

          2、PageController

          @Controller public class PageController { @Resource private UserMapper userMapper; @RequestMapping(value = "/login", method = RequestMethod.GET) public String Login(){ return "login"; } @RequestMapping(value = "/user/index", method = RequestMethod.GET) public String Index(Model model){ User user = (User) SecurityUtils.getSubject().getPrincipal(); User uuser = userMapper.getUserById(user.getId()); if (StringUtils.isEmpty(user)) { return "redirect:/login"; } model.addAttribute("user", uuser); return "index"; } }

          3、MenuController

          /** * @author wxhntmy */ @RestController public class MenuController { @Resource private RoleMapper roleMapper; @Resource private UserRoleListMapper userRoleListMapper; //记住用户菜单收展 private Map<String, Map> menu_map = new HashMap<>(); @RequestMapping(value = "/menu", method = RequestMethod.GET) public Msg<List<Menu>> getMenu() { User user = (User) SecurityUtils.getSubject().getPrincipal(); List<Menu> list = new ArrayList<>(); if (StringUtils.isEmpty(user)) { return Msg.fail(list, "登录信息已过期!请重新登录!"); } //记住收展 Map<String, Boolean> map_store = new HashMap<>(); if (menu_map.containsKey(user.getId())){ map_store = menu_map.get(user.getId()); } Map<String, String> map = new HashMap(); List<UserRoleList> roleLists = userRoleListMapper.getUserRoleByUserId(user.getId()); for (UserRoleList roleList : roleLists) { Role role = roleMapper.getRoleByRoleId(roleList.getRole_id()); map.put(role.getRole(), roleList.getUser_id()); } Menu menu1 = new Menu(); menu1.setName("首页"); menu1.setIcon("&#xe68e;"); menu1.setUrl("/user/index"); menu1.setHidden(false); List<MenuList> menuLists2 = new ArrayList<>(); menu1.setList(menuLists2); list.add(menu1); if (map.containsKey("student")){ Menu menu2 = new Menu(); Menu menu3 = new Menu(); menu2.setName("课程管理"); menu2.setIcon("&#xe609;"); menu2.setUrl(""); menu2.setHidden(map_store.getOrDefault("课程管理", false)); List<MenuList> menuLists = new ArrayList<>(); MenuList menuList1 = new MenuList("已选课程", ""); MenuList menuList2 = new MenuList("可选课程", ""); MenuList menuList22 = new MenuList("课程论坛", ""); menuLists.add(menuList1); menuLists.add(menuList2); menuLists.add(menuList22); menu2.setList(menuLists); menu3.setName("成果管理"); menu3.setIcon("&#xe609;"); menu3.setUrl(""); menu3.setHidden(map_store.getOrDefault("成果管理", false)); List<MenuList> menuLists3 = new ArrayList<>(); MenuList menuList3 = new MenuList("提交课程成果", ""); MenuList menuList33 = new MenuList("提交课程日志", ""); MenuList menuList4 = new MenuList("实训课程成绩", ""); MenuList menuList5 = new MenuList("课程调查问卷", ""); menuLists3.add(menuList3); menuLists3.add(menuList33); menuLists3.add(menuList4); menuLists3.add(menuList5); menu3.setList(menuLists3); list.add(menu2); list.add(menu3); } if (map.containsKey("teacher")){ Menu menu2 = new Menu(); Menu menu3 = new Menu(); menu2.setName("授课课程管理"); menu2.setIcon("&#xe609;"); menu2.setUrl(""); menu2.setHidden(map_store.getOrDefault("授课课程管理", false)); List<MenuList> menuLists = new ArrayList<>(); MenuList menuList1 = new MenuList("教授的实训课程", ""); MenuList menuList2 = new MenuList("课程论坛", ""); menuLists.add(menuList1); menuLists.add(menuList2); menu2.setList(menuLists); menu3.setName("成果管理"); menu3.setIcon("&#xe609;"); menu3.setUrl(""); menu3.setHidden(map_store.getOrDefault("成果管理", false)); List<MenuList> menuLists3 = new ArrayList<>(); MenuList menuList3 = new MenuList("课程成果检查", ""); MenuList menuList33 = new MenuList("课程日志批复", ""); MenuList menuList4 = new MenuList("实训课程成绩", ""); menuLists3.add(menuList3); menuLists3.add(menuList33); menuLists3.add(menuList4); menu3.setList(menuLists3); list.add(menu2); list.add(menu3); } if (map.containsKey("professionor")){ Menu menu2 = new Menu(); Menu menu3 = new Menu(); menu2.setName("实训课程管理"); menu2.setIcon("&#xe609;"); menu2.setUrl(""); menu2.setHidden(map_store.getOrDefault("实训课程管理", false)); List<MenuList> menuLists = new ArrayList<>(); MenuList menuList1 = new MenuList("待批准实训课程", ""); MenuList menuList2 = new MenuList("添加实训课程", ""); MenuList menuList3 = new MenuList("实训课程管理", ""); menuLists.add(menuList1); menuLists.add(menuList2); menuLists.add(menuList3); menu2.setList(menuLists); menu3.setName("发布调查问卷"); menu3.setIcon("&#xe609;"); menu3.setUrl(""); menu3.setHidden(map_store.getOrDefault("发布调查问卷", false)); List<MenuList> menuLists1 = new ArrayList<>(); MenuList menuList11 = new MenuList("发布调查问卷", ""); MenuList menuList21 = new MenuList("回收调查问卷", ""); menuLists1.add(menuList11); menuLists1.add(menuList21); menu3.setList(menuLists1); list.add(menu2); list.add(menu3); } if (map.containsKey("admin")){ Menu menu2 = new Menu(); Menu menu3 = new Menu(); menu2.setName("用户管理"); menu2.setIcon("&#xe612;"); menu2.setUrl(""); menu2.setHidden(map_store.getOrDefault("用户管理", false)); List<MenuList> menuLists = new ArrayList<>(); MenuList menuList0 = new MenuList("添加用户", ""); MenuList menuList1 = new MenuList("学生账号", ""); MenuList menuList2 = new MenuList("教师账号", ""); MenuList menuList3 = new MenuList("实训负责人账号", ""); menuLists.add(menuList0); menuLists.add(menuList1); menuLists.add(menuList2); menuLists.add(menuList3); menu2.setList(menuLists); menu3.setName("数据库管理"); menu3.setIcon("&#xe857;"); menu3.setUrl(""); menu3.setHidden(map_store.getOrDefault("数据库管理", false)); List<MenuList> menuLists3 = new ArrayList<>(); MenuList menuList4 = new MenuList("备份数据库", ""); MenuList menuList5 = new MenuList("还原数据库", ""); menuLists3.add(menuList4); menuLists3.add(menuList5); menu3.setList(menuLists3); list.add(menu2); list.add(menu3); } Menu menu4 = new Menu(); menu4.setName("系统设置"); menu4.setIcon("&#xe620;"); menu4.setUrl(""); menu4.setHidden(map_store.getOrDefault("系统设置", false)); List<MenuList> menuLists4 = new ArrayList<>(); MenuList menuList5 = new MenuList("修改个人信息", ""); MenuList menuList6 = new MenuList("修改密码", ""); MenuList menuList7 = new MenuList("清除缓存", ""); menuLists4.add(menuList5); menuLists4.add(menuList6); menuLists4.add(menuList7); menu4.setList(menuLists4); Menu menu5 = new Menu(); menu5.setName("退出登录"); menu5.setIcon("&#xe65c;"); menu5.setUrl("/logout"); menu5.setHidden(false); List<MenuList> menuLists5 = new ArrayList<>(); menu5.setList(menuLists5); list.add(menu4); list.add(menu5); if (map.containsKey("student")){ return Msg.ok(list, "STU"); } String message = null; if (map.containsKey("teacher")){ message = "TEA"; } if (map.containsKey("professionor")){ message = "PRI"; } if (map.containsKey("admin")){ message = "ADM"; } return Msg.ok(list, message); } @RequestMapping(value = "/menu_storage", method = RequestMethod.GET) public Msg<String> menu_storage(@RequestParam String data) { JSONArray jsonArray = JSONArray.parseArray(data); User user = (User) SecurityUtils.getSubject().getPrincipal(); if (StringUtils.isEmpty(user)) { return Msg.fail("登录信息已过期!请重新登录!"); } //记住收展 Map<String, Boolean> map_store = new HashMap<>(); for (Object o : jsonArray) { JSONObject jsonObject = JSONObject.parseObject(o.toString()); map_store.put(jsonObject.getString("name"), Boolean.valueOf(jsonObject.getString("hidden"))); } menu_map.put(user.getId(), map_store); return Msg.ok(); } }

          五、数据库

          1、user 表

          2、role 表

          3、user_role_list 表

          六、前端页面

          1、Ajax 请求菜单数据

          let config = {}; function set_menu() { //ajax提交信息 $.ajax({ type: "get", async: false, url: "/menu",// 请求发送到LoginServlet处 dataType: 'json', success: function (msg) { if (msg.ok === true && msg.data) { config["name"] = msg.message; config["menu"] = msg.data; } if (msg.ok === false) { window.location.href = "/logout"; } if (!msg.data) { window.location.href = "/logout"; } }, error: function (msg) { // 请求失败时执行该函数 layer.alert('请求菜单数据失败!!!', function (index) { //do something layer.close(index); }); } }); } set_menu(); $(document).ready(function () { //删除 $(".del").click(function () { var url = $(this).attr("href"); var id = $(this).attr("data-id"); layer.confirm('你确定要删除么?', { btn: ['确定', '取消'] }, function () { $.get(url, function (data) { if (data.code === 1) { $(id).fadeOut(); layer.msg(data.msg, {icon: 1}); } else { layer.msg(data.msg, {icon: 2}); } }); }, function () { layer.msg("您取消了删除!"); }); return false; }); }) layui.use('form', function () { var form = layui.form, layer = layui.layer; }); var vue = new Vue({ el: '#app', data: { webname: config.name, menu: [], address: [] }, created: function () { this.menu = config.menu; this.thisActive(); this.thisAttr(); }, methods: { //记住收展 onActive: function (pid, id = false) { let data; if (id === false) { data = this.menu[pid]; if (data.url.length > 0) { this.menu.forEach((v, k) => { v.active = false; v.list.forEach((v2, k2) => { v2.active = false; }) }) data.active = true; } data.hidden = !data.hidden; } else { this.menu.forEach((v, k) => { v.active = false; v.list.forEach((v2, k2) => { v2.active = false; }) }) data = this.menu[pid].list[id]; } this.updateStorage(); if (data.url.length > 0) { if (data.target) { if (data.target === '_blank') { window.open(data.url); } else { window.location.href = data.url; } } else { window.location.href = data.url; } } }, //更新菜单缓存 updateStorage() { //sessionStorage.menu = JSON.stringify(this.menu); $.ajax({ type: "get", async: false, url: "/menu_storage",// 请求发送到LoginServlet处 data: { "data": JSON.stringify(this.menu) }, dataType: 'json', success: function (msg) { }, error: function (msg) { // 请求失败时执行该函数 var index = layer.load(); layer.close(index); layer.alert('请求菜单数据失败!!!', function (index) { //do something layer.close(index); }); } }); }, //菜单高亮 thisActive: function () { let pathname = window.location.pathname; let host = window.location.host; let pid = false; let id = false; this.menu.forEach((v, k) => { let url = v.url; if (url.length > 0) { if (url[0] !== '/' && url.substr(0, 4) !== 'http') { url = '/' + url; } } if (pathname === url) { pid = k; } v.list.forEach((v2, k2) => { let url = v2.url; if (url.length > 0) { if (url[0] !== '/' && url.substr(0, 4) !== 'http') { url = '/' + url; } } if (pathname === url) { pid = k; id = k2; } }) }) if (id !== false) { this.menu[pid].list[id].active = true; } else { if (pid !== false) { this.menu[pid].active = true; } } this.updateStorage(); }, //当前位置 thisAttr: function () { //当前位置 let address = [{ name: '首页', url: '/user/index' }]; this.menu.forEach((v, k) => { v.list.forEach((v2, k2) => { if (v2.active) { address.push({ name: v.name, url: 'javascript:;' }) address.push({ name: v2.name, url: v2.url, }) this.address = address; } }) }) } } })

          2、显示菜单栏

          <ul class="cl"> <!--顶级分类--> <li v-for="vo,index in menu" :class="{hidden:vo.hidden}"> <a href="javascript:;" rel="external nofollow" rel="external nofollow" :class="{active:vo.active}" @click="onActive(index)"> <i class="layui-icon" v-html="vo.icon"></i> <span v-text="vo.name"></span> <i class="layui-icon arrow" v-show="vo.url.length==0">&#xe61a;</i> <i v-show="vo.active" class="layui-icon active">&#xe623;</i> </a> <!--子级分类--> <div v-for="vo2,index2 in vo.list"> <a href="javascript:;" rel="external nofollow" rel="external nofollow" :class="{active:vo2.active}" @click="onActive(index,index2)" v-text="vo2.name"></a> <i v-show="vo2.active" class="layui-icon active">&#xe623;</i> </div> </li> </ul>

          七、完整代码

          完整代码转 Gitee:wxhntmy / SpringBootLayuiMenu

          到此这篇关于LayUI+Shiro实现动态菜单并记住菜单收展的示例的文章就介绍到这了,更多相关LayUI Shiro动态菜单内容请搜索自由互联以前的文章或继续浏览下面的相关文章希望大家以后多多支持自由互联!