LayUI+Shiro實現動態選單並記住選單收展的示例
阿新 • • 發佈:2021-05-07
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 shiroFilt程式設計客棧erFactoryBean; } /** * 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("實訓負責人賬號",""); menu程式設計客棧Lists.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",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] !== '/' &&http://www.cppcns.comamp; 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">&lhttp://www.cppcns.comt;/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
到此這篇關於LayUI+Shiro實現動態選單並記住選單收展的示例的文章就介紹到這了,更多相關LayUI Shiro動態選單內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!