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 配置
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(\"\"); 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(\"\"); 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(\"\"); 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(\"\"); 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(\"\"); 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(\"\"); 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(\"\"); 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(\"\"); 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(\"\"); 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(\"\"); 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(\"\"); 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\"></i> <i v-show=\"vo.active\" class=\"layui-icon active\"></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\"></i> </div> </li> </ul>
七、完整代码
完整代码转 Gitee:wxhntmy / SpringBootLayuiMenu
© 版权声明
THE END
暂无评论内容