- 今天內容安排:
- 1、權限管理(初始化、查詢、添加)
- 2、角色管理(添加、查詢)
- 3、用戶管理(添加、查詢)
- 4、修改自定義BOSRealm中的授權方法(基於數據庫實現)
- 5、使用ehcache 緩存權限數據
- 6、系統的左側菜單根據當前登錄用戶的權限動態展示
1、權限管理(初始化、查詢、添加)
注意1:權限數據屬於比較特殊的數據,系統在上線之後,必須先把權限數據給它初始化到數據庫中去,然後這個系統纔可以跑起來。如果不初始化權限數據的話,那麼登錄上系統之後,會發現一個菜單也沒有,什麼也不能幹。所以說,所有的系統在上線的時候都會進行權限數據的初始化。
注意2:我們的初始化文件數據一般都會整理成一個sql腳本文件,系統上線之後,首先去數據庫中去執行這個sql腳本文件,執行完之後,我們的數據庫中就有數據了,然後整個系統才能正常運行。即:系統的正常運行是要依賴一些基礎數據的
。
1.1、初始化權限數據
第一步:執行sql腳本文件auth_function.sql初始化權限數據
1.2、權限的分頁查詢
文件位置:/bos19/WebContent/WEB-INF/pages/admin/function.jsp 第一步:修改頁面中datagrid的URL地址,訪問FunctionAction的pageQuery()的分頁查詢方法,並加入分頁條
第二步:創建FunctionAction類,提供pageQuery()的分頁查詢方法
package com.itheima.bos.web.action; import java.io.IOException; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Controller; import com.itheima.bos.domain.Function; import com.itheima.bos.web.action.base.BaseAction; /** * 權限管理 * @author Bruce * */ @Controller @Scope("prototype") public class FunctionAction extends BaseAction<Function> { /** * 權限的分頁查詢 * @return * @throws IOException */ public String pageQuery() throws IOException { functionService.pageQuery(pageBean); String[] excludes = new String[] {"currentPage", "pageSize", "detachedCriteria", "function", "functions", "roles"}; this.writePageBean2Json(pageBean, excludes); return "none"; } }
第三步:配置struts.xml
<!-- 權限管理:配置functionAction--> <action name="functionAction_*" class="functionAction" method="{1}"> </action>
解決分頁查詢問題:
問題:分頁對象pageBean中有一個page屬性,模型對象Function中也有一個page屬性,struts框架會把頁面提交過來的參數(是字符串)優先給模型對象中的page(是字符串)設置值
,BaseAction中的page屬性(是int類型)就賦值不成功,一直是默認值0。
說明數據庫的表設計是有問題的。
方式一
:修改數據庫中權限表的字段名稱和對應的權限類中的屬性以及對應的映射文件。(推薦使用此方法,需要修改數據庫表)
方式二
:修改權限類Function.java中的屬性page名稱爲新名稱,再去修改映射文件Function.hbm.xml,讓新名稱依舊對應數據庫權限表中page字段。(此方法也可以,不需要修改數據庫表)
方式三
:從model對象(Function)中獲取page注入到pageBean對象中。(不推薦使用,但是本案例這樣使用)
修改權限的分頁查詢代碼,如下所示:
/** * 權限的分頁查詢 * @return * @throws IOException */ public String pageQuery() throws IOException { // 從model對象中獲取page注入到pageBean對象中 String page = model.getPage(); pageBean.setCurrentPage(Integer.parseInt(page)); functionService.pageQuery(pageBean); String[] excludes = new String[] {"currentPage", "pageSize", "detachedCriteria", "function", "functions", "roles"}; this.writePageBean2Json(pageBean, excludes); return "none"; }
1.3、權限的添加功能
文件位置:/bos19/WebContent/WEB-INF/pages/admin/function_add.jsp 第一步:修改添加頁面中的combobox的URL地址,查詢所有的權限,並展示到下拉框中
<tr> <td>父功能點</td> <td> <input name="function.id" class="easyui-combobox" data-options="valueField:'id',textField:'name', url:'${pageContext.request.contextPath}/functionAction_listajax.action'"/> </td> </tr>
第二步:在FunctionAction中提供listajax()方法
/** * 添加權限 * @return */ public String add() { functionService.save(model); return "list"; }
瀏覽器效果如下圖所示:
第三步:修改combobox的name屬性,爲了便於封裝數據,這樣父功能點對應的的id的值就可以封裝進Function裏去了。
第四步:爲保存按鈕綁定事件提交表單
<script type="text/javascript"> $(function(){ // 點擊保存 $('#save').click(function() { // 對form表單進行校驗,並提交 if ($('#functionForm').form('validate')) { $('#functionForm').submit(); } }); }); </script> ...... <form id="functionForm" method="post" action="${pageContext.request.contextPath}/functionAction_add.action">
瀏覽器效果如下圖所示:
第五步:在Action中提供add()方法,保存一個權限數據
/** * 添加權限 * @return */ public String add() { functionService.save(model); return "list"; }
Service層代碼:
public void save(Function model) { Function function = model.getFunction(); // 注意:String類型作爲主鍵時,容易出現空串的情況,我們需要排除。 if (function != null && function.getId().equals("")) { // 沒有上級,說明是頂級目錄 model.setFunction(null); } functionDao.save(model); }
2、角色管理(添加、查詢)
2.1、角色的添加功能
文件位置:/bos19/WebContent/WEB-INF/pages/admin/role_add.jsp 第一步:使用ztree展示權限樹,啓用ztree的勾選效果
// 授權樹初始化 var setting = { data : { key : { title : "t" }, simpleData : { // 啓用簡單json數據描述節點數據 enable : true } }, check : { // 啓用ztree的勾選效果 enable : true, } };
第二步:頁面上的數據要來自數據庫,所以我們需要修改ajax方法的URL地址,訪問Action,查詢所有的權限數據,並返回簡單json數據作爲ztree的節點數據
// 發送ajax請求獲取菜單數據構造ztree // 若爲“text”文本數據,需要轉成json數據纔可以使用 $.ajax({ url : '${pageContext.request.contextPath}/functionAction_listajax.action', type : 'POST', dataType : 'text', success : function(data) { var zNodes = eval("(" + data + ")"); $.fn.zTree.init($("#functionTree"), setting, zNodes); }, error : function(msg) { alert('樹加載異常!'); } }); // 發送ajax請求獲取菜單數據構造ztree // 若爲“json”數據,則不需要轉換,直接使用即可 $.ajax({ url : '${pageContext.request.contextPath}/functionAction_listajax.action', type : 'POST', dataType : 'json', success : function(data) { $.fn.zTree.init($("#functionTree"), setting, json); }, error : function(msg) { alert('樹加載異常!'); } });
注意:我們發現瀏覽器頁面上展示的數據沒有正常顯示出來上下級關係,如下圖所示:
爲什麼呢?答:這是由於響應的json數據中,沒有鍵pId以及對應的值,那麼我們就需要在實體類Function.java中臨時添加一個getter()方法(我們姑且可以把它看作爲臨時的屬性吧),示例代碼如下:
// 響應json數據之前,我們先進行序列化,而序列化,找的就是實體類中的getter()方法 public String getpId() { if (function != null) { return function.getId(); } else { return "0"; // 參考/bos19/WebContent/json/menu.json 中json的寫法 } }
增加後,瀏覽器頁面的顯示爲:
第三步:爲保存
按鈕綁定事件,提交表單,發現ztree選中的節點沒有提交,爲什麼呢?答:因爲顯示在頁面上僅僅只是ztree的頁面效果
而已,不是真正的表單控件<input type="checkbox">
。如何解決呢?答:我們需要使用ztree提供的API獲得當前選中的節點,賦值給指定的隱藏域。
// 點擊保存按鈕 $('#save').click(function(){ // location.href='${pageContext.request.contextPath}/page_admin_privilege.action'; var v = $("#roleForm").form("validate"); if (v) { var treeObj = $.fn.zTree.getZTreeObj("functionTree"); // 獲得頁面中的ztree對象 var nodes = treeObj.getCheckedNodes(true); // 在提交表單之前將選中的checkbox收集 var array = new Array(); for (var i = 0; i < nodes.length; i++) { array.push(nodes[i].id); } var functionIds = array.join(","); // 11,112,113,12,121,13,131 $("input[name=functionIds]").val(functionIds); // 賦值給指定的隱藏域 $("#roleForm").submit(); } }); ...... <form id="roleForm" method="post" action="${pageContext.request.contextPath}/roleAction_add.action"> <!-- 設置隱藏域 --> <input type="hidden" name="functionIds">
瀏覽器效果如下圖所示:
第四步:創建RoleAction,提供add()方法
package com.itheima.bos.web.action; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Controller; import com.itheima.bos.domain.Role; import com.itheima.bos.web.action.base.BaseAction; /** * 角色設置 * @author Bruce * */ @Controller @Scope("prototype") public class RoleAction extends BaseAction<Role> { // 採用屬性驅動的方式,接收瀏覽器頁面傳過來的functionIds private String functionIds; public void setFunctionIds(String functionIds) { this.functionIds = functionIds; } /** * 添加角色 * @return */ public String add() { roleService.save(model, functionIds); return "list"; } }
Service層代碼:
package com.itheima.bos.service.impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.itheima.bos.dao.IFunctionDao; import com.itheima.bos.dao.IRoleDao; import com.itheima.bos.domain.Function; import com.itheima.bos.domain.Role; import com.itheima.bos.service.IRoleService; @Service @Transactional public class RoleServiceImpl implements IRoleService { // 注入角色dao @Autowired private IRoleDao roleDao; // 注入權限dao @Autowired private IFunctionDao functionDao; public void save(Role model, String functionIds) { // 11,112,113,12,121,13,131 roleDao.save(model); // 使Role變成持久化對象 // 角色跟權限是多對多關係,我們現在要建立關聯,對於多對多關係,誰關聯誰都可以,但是我們要有意識的去查看下Hibernate映射文件,看看誰放棄了維護外鍵的權利 // 通過查看映射文件可知:角色關聯權限 String[] ids = functionIds.split(","); for (String fid : ids) { // 方式一:自己去new(即構造)一個Function對象,效率高,因爲我們關聯是真正靠fid進行關聯的 Function function = new Function(fid); // 託管態對象:離線對象 model.getFunctions().add(function); // 方式二:根據權限id把權限對象查詢出來,再讓角色對象model去關聯權限對象Function,需要發sql語句,效率低一些 // Function function = functionDao.findById(fid); // 持久化對象 // model.getFunctions().add(function); // 關聯完之後,事務提交之後,Hibernate框架會給數據庫發sql,會自動更新數據庫,即根據快照去對比,看看我們取出來的持久化對象是否跟快照長得不一樣,若不一樣,就刷新緩存。 } } }
第五步:配置struts.xml
<!-- 角色管理:配置roleAction--> <action name="roleAction_*" class="roleAction" method="{1}"> <result name="list">/WEB-INF/pages/admin/role.jsp</result> </action>
2.2、角色的分頁查詢
文件位置:/bos19/WebContent/WEB-INF/pages/admin/role.jsp 方法步驟同權限的分頁查詢,不在贅述!
3、用戶管理(添加、查詢)
3.1、用戶的分頁查詢
文件位置:/bos19/WebContent/WEB-INF/pages/admin/userlist.jsp 方法步驟同權限的分頁查詢,不在贅述!
3.2、用戶的添加功能
文件位置:/bos19/WebContent/WEB-INF/pages/admin/userinfo.jsp 第一步:發送ajax請求,從數據庫中獲取所有的角色數據,返回json數據,在瀏覽器頁面中動態構造到checkbox中
<tr> <td>選擇角色:</td> <td colspan="3"> <script type="text/javascript"> $(function() { // 發送ajax請求,從數據庫中獲取所有的角色數據,返回json數據,在瀏覽器頁面中動態構造到checkbox中 var url = "${pageContext.request.contextPath}/roleAction_listajax.action"; $post(url,{},function(data) { // 回調函數,解析返回的json數據,動態構造checkbox對象 // ...... },'json'); }); </script> <!-- 示例僞代碼 --> <input type="checkbox" name="roleIds" value="roleId">roleName <input type="checkbox" name="roleId">管理員</br> <input type="checkbox" name="roleId">收派員</br> <input type="checkbox" name="roleId">用戶(業務員)</br> </td> </tr>
瀏覽器debug調試結果爲:
第二步:在RoleAction中提供listajax()方法
/** * 查詢所有角色 * @return * @throws IOException */ public String listajax() throws IOException { List<Role> list = roleService.findAll(); String[] excludes = new String[] {"currentPage", "pageSize", "detachedCriteria", "users", "functions"}; this.writeList2Json(list, excludes); return "none"; }
第三步:完善ajax方法的回調函數
<tr> <td>選擇角色:</td> <td colspan="3"> <script type="text/javascript"> $(function() { // 發送ajax請求,從數據庫中獲取所有的角色數據,返回json數據,在瀏覽器頁面中動態構造到checkbox中 var url = "${pageContext.request.contextPath}/roleAction_listajax.action"; $post(url,{},function(data) { // 回調函數,解析返回的json數據,動態構造checkbox對象 for (var i = 0; i < data.length; i++) { var roleId = data[i].id; var roleName = data[i].name; $("#roleTD").append('<input type="checkbox" value="' + roleId + '" name="roleIds">' + roleName); } }, 'json'); // 最後一個參數“json”可以不用寫,因爲在BaseAction中,服務器向瀏覽器寫數據的時候已經setContentType中設置過了。 }); </script> </td> </tr>
瀏覽器效果如下圖所示:
第四步:爲保存
按鈕綁定事件,並提交表單
瀏覽器效果如下圖所示:
第五步:在UserAction中提供add()方法,保存用戶
/** * 保存用戶 * @return */ public String add() { userServie.save(model, roleIds); return "list"; }
Service層:
public void save(User model, String[] roleIds) { // 將user持久化之前,要先將用戶密碼進行加密 String password = model.getPassword(); password = MD5Utils.md5(password); model.setPassword(password); userDao.save(model); // 將User持久化 // 用戶跟角色是多對多關係,我們現在要建立關聯,對於多對多關係,誰關聯誰都可以,但是我們要有意識的去查看下Hibernate映射文件,看看誰放棄了維護外鍵的權利 // 通過查看映射文件可知:用戶 關聯 角色 for (String roleId : roleIds) { // 用戶 關聯 角色 // 方式一:自己去new(即構造)一個Role對象,效率高,因爲我們關聯是真正靠roleId進行關聯的 Role role = new Role(roleId); model.getRoles().add(role); // 方式二:根據角色id把角色對象查詢出來,再讓用戶對象model去關聯角色對象Role,需要發sql語句,效率低一些 // Role role = roelDao.findById(roleId); // 持久化對象 // model.getRoles().add(role); // 關聯完之後,事務提交之後,Hibernate框架會給數據庫發sql,會自動更新數據庫,即根據快照去對比,看看我們取出來的持久化對象是否跟快照長得不一樣,若不一樣,就刷新緩存。 } }
第六步:配置struts.xml,做頁面跳轉
<!-- 用戶管理:配置userAction--> <action name="userAction_*" class="userAction" method="{1}"> <!-- <result name="login">/login.jsp</result> --> <result name="home">/index.jsp</result> <result name="list">/WEB-INF/pages/admin/userlist.jsp</result> </action>
第七步:在User類中提供getRoleNames()方法和getFormatBirthday()方法,使得服務器返回給瀏覽器的json數據中含有roleNames字段和formateBrithday字段
第八步:修改userlist.jsp頁面中對應的field字段名稱
瀏覽器運行結果:
4、修改自定義BOSRealm中的授權方法(基於數據庫實現)
/** * 授權方法 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { // 創建SimpleAuthenticationInfo簡單授權信息對象,使用沒有參數的構造方法 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); // 爲當前用戶授予staff權限(硬編碼)--> perms["staff"] // info.addStringPermission("staff"); // 爲當前用戶授予staff角色(硬編碼)--> roles["staff"] // info.addRole("staff"); // TODO 根據當前登錄用戶查詢數據庫,獲取其對應的權限數據,再進行授權(軟編碼) // 獲取當前登錄用戶對象 User user = (User) principals.getPrimaryPrincipal(); List<Function> list = null; if (user.getUsername().equals("admin")) { // 說明是超級管理員用戶,則需要查詢到所有權限,給其授權 list = functionDao.findAll(); } else { // 說明是普通用戶,則需要根據用戶id查詢對應的權限 list = functionDao.findListByUserId(user.getId()); } // 授權 for (Function function : list) { info.addStringPermission(function.getCode()); } return info; }
在FunctionDao中提供根據用戶id查詢權限的方法:
/** * 根據用戶id查詢對應的權限 * * 我們已經知道,用戶跟權限沒有直接對應關係,但是可以通過權限找到角色,再通過角色找到用戶 */ public List<Function> findListByUserId(String userId) { // 左外連接查詢 String hql = "select distinct f from Function f left outer join f.roles r left outer join r.users u where u.id=?"; return this.getHibernateTemplate().find(hql, userId); }
5、使用ehcache 緩存權限數據
第一步:導入ehcache的jar包到項目中 第二步:提供ehcache的xml配置文件(可以從其jar包中獲得),刪除掉其中的註釋
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd"> <diskStore path="java.io.tmpdir"/> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" maxElementsOnDisk="10000000" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU" /> </ehcache>
第三步:在spring配置文件中註冊一個緩存管理器,並注入給安全管理器
6、系統左側菜單根據當前登錄用戶的權限動態展示
第一步:修改index.jsp頁面中ajax方法的URL
// 基本功能菜單加載 $.ajax({ url : '${pageContext.request.contextPath}/functionAction_findMenu.action', type : 'POST', // dataType : 'text', dataType : 'json', success : function(data) { // var zNodes = eval("(" + data + ")"); $.fn.zTree.init($("#treeMenu"), setting, data); }, error : function(msg) { alert('菜單加載異常!'); } });
第二步:在FunctionAction中提供findMenu()方法
/** * 根據當前登錄用戶查詢對應的菜單數據(從權限表中查詢) * @return * @throws IOException */ public String findMenu() throws IOException { List<Function> list = functionService.findMenu(); String[] excludes = new String[] {"currentPage", "pageSize", "detachedCriteria", "function", "functions", "roles"}; this.writeList2Json(list, excludes); return "none"; }
第三步:在FunctionService中提供方法
public List<Function> findMenu() { User user = BOSContext.getLoginUser(); List<Function> list = null; if (user.getUsername().equals("admin")) { // 說明是超級管理員 list = functionDao.findAllMenu(); } else { // 說明是普通用戶 list = functionDao.findMenuById(user.getId()); } return list; }
第四步:在FunctionDao中擴展方法
/** * 根據用戶id查詢對應的權限,再查詢對應的菜單並按照優先級排序 */ public List<Function> findMenuById(String userId) { String hql = "select distinct f from Function f left outer join f.roles r left outer join r.users u where u.id=? and f.generatemenu='1' order by f.zindex"; return this.getHibernateTemplate().find(hql, userId); }