Target="right" 而這個right對應name="right"的地方.這樣一來,在第二行左邊點擊超連接,就能夠在右邊顯示頁面了.javascript
先將靜態頁面的五個頁面(主頁,上下左右頁面)複製到新建在jsp中的homeAction文件夾中,css
本來的靜態頁面都是html,要將html改成jsp:將jsp的約束代碼和導入公共頁面的代碼複製進html中,再將後綴名都改成.jsphtml
新建一個HomeAction:java
@Controller程序員 public class HomeAction extends ActionSupport{web public String index() throws Exception {spring return "index";數據庫 }apache public String top() throws Exception {緩存 return "top"; } public String bottom() throws Exception { return "bottom"; } public String left() throws Exception { return "left"; } public String right() throws Exception { return "right"; } } |
Tips:這個action只作跳轉的做用,因此只須要繼承ActionSupport.
開多例@Scope("prototype")是爲了線程安全,而在這個action中沒有公共的資源,因此不存在線程安全問題,也就不用開多例了.
Struts.sml:
<!-- 首頁 --> <action name="home_*" class="homeAction" method="{1}"> <result name="{1}">/WEB-INF/jsp/homeAction/{1}.jsp</result> </action> |
Tips:由於action中每一個方法的返回值都和方法同名,因此能夠這麼寫.
通常用戶訪問的是放在外面的index.jsp,而後再由這個頁面跳轉到被深藏起來的index.jsp
因此放在外面的index.jsp只有跳轉的做用.
(放在外面的)Index.jsp:
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%> <% response.sendRedirect(request.getContextPath() + "/home_index.do"); %> |
(放在homeAction中的)Index.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <html> <head> <title>ItcastOA</title> <%@ include file="/WEB-INF/jsp/public/header.jspf" %> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> </head>
<frameset rows="100,*,25" framespacing="0" border="0" frameborder="0"> <frame src="home_top.do" name="TopMenu" scrolling="no" noresize /> <frameset cols="180,*" id="resize"> <frame noresize name="menu" src="home_left.do" scrolling="yes" /> <frame noresize name="right" src="home_right.do" scrolling="yes" /> </frameset> <frame noresize name="status_bar" scrolling="no" src="home_bottom.do" /> </frameset>
<noframes> <body> </body> </noframes> </html> |
這裏的連接,如home_top.do,還要通過action,再跳到相應jsp頁面.
修改下top.jsp:
<div id="Head1Right"> <div id="Head1Right_UserName"> <img border="0" width="13" height="14" src="style/images/top/user.gif" />您好,<b>${user.name }</b> </div> <div id="Head1Right_SystemButton"> <s:a target="_parent" action="loginout_logout"> <img width="78" height="20" alt="退出系統" src="style/blue/images/top/logout.gif" /> </s:a> </div> |
這行代碼在這裏的做用是:當退出系統時,註銷頁面會佔用整個頁面,而不是隻在top(第一行)顯示!!!
左側菜單是二級菜單,權限數據不會被改變,因此能夠採用兩重遍歷的方式來實現左側菜單.
Left.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <html> <head> <title>導航菜單</title> <%@ include file="/WEB-INF/jsp/public/header.jspf"%> <link type="text/css" rel="stylesheet" href="style/blue/menu.css" /> </head> <body style="margin: 0"> <div id="Menu"> <ul id="MenuUl"> <!-- 第一級權限 --> <s:iterator value="topPrivilegeList"> <li class="level1"> <div onClick="menuClick(this)" class="level1Style"> <img src="style/images/MenuIcon/FUNC20001.gif" class="Icon" />${name } </div> <ul style="display: none;" class="MenuLevel2"> <!-- 第二級權限 --> <s:iterator value="children"> <li class="level2"> <div class="level2Style"> <img src="style/images/MenuIcon/menu_arrow_single.gif" /> ${name } </div> </li> </s:iterator> </ul> </s:iterator> </ul> </div> </body> </html> |
在util包中新建一個OAInitListener:
@Component public class OAInitListener implements ServletContextListener {
private Log log =LogFactory.getLog(OAInitListener.class);
@Resource private PrivilegeService privilegeService;
//初始化 public void contextInitialized(ServletContextEvent sec) {
List<Privilege> topPrivilegeList = privilegeService.findtopPrivilegeList(); ActionContext.getContext().put("topPrivilegeList", topPrivilegeList); log.info("======= topPrivilegeList已經放到application做用域中了! =======");
}
//銷燬 public void contextDestroyed(ServletContextEvent sec) { }
} |
建立監聽器以後,還得在web.xml中配置:
<!-- 配置Spring的用於初始化ApplicationContext對象的監聽器 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext*.xml</param-value> </context-param>
<!-- 配置本身的用於初始化的監聽器,必定要配置到spring的ContextLoaderListener後面,由於要用到spring容器對象 (先寫的先配置) --> <listener> <listener-class>cn.itcast.oa.util.OAInitListener</listener-class> </listener>
<!-- 配置Spring的OpenSessionInViewFilter以解決懶加載異常的問題 --> <filter> <filter-name>OpenSessionInViewFilter</filter-name> <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class> </filter> <filter-mapping> <filter-name>OpenSessionInViewFilter</filter-name> <url-pattern>*.do</url-pattern> </filter-mapping>
<!-- 配置Struts2的核心的過濾器 --> <filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> |
雖然把OAInitListener放到了spring容器中,也注入了service,可是web.xml和spring沒有關聯,其中配置監聽器的代碼不是從容器中拿對象,而是用類全名經過反射的方式建立實例對象,因此OAInitListener根本拿不到service.
正確的OAInitListener:
public class OAInitListener implements ServletContextListener {
private Log log =LogFactory.getLog(OAInitListener.class);
//初始化 public void contextInitialized(ServletContextEvent sce) { ServletContext application = sce.getServletContext();
//從spring的容器中取出privilegeService的對象實例 WebApplicationContext ac = WebApplicationContextUtils.getWebApplicationContext(application); PrivilegeService privilegeService = (PrivilegeService) ac.getBean("privilegeServiceImpl");
//查詢全部頂級的權限列表,並放到application做用域中 List<Privilege> topPrivilegeList = privilegeService.findtopPrivilegeList(); application.setAttribute("topPrivilegeList", topPrivilegeList); log.info("======= topPrivilegeList已經放到application做用域中了! =======");
}
//銷燬 public void contextDestroyed(ServletContextEvent sce) { } } |
由於在left.jsp中是這樣獲取topPrivilegeList的: <s:iterator value="topPrivilegeList">.
這是ognl表達式,這麼寫並拿不到存在application做用域中的topPrivilegeList.應該改成
Left.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <html> <head> <title>導航菜單</title> <%@ include file="/WEB-INF/jsp/public/header.jspf"%> <link type="text/css" rel="stylesheet" href="style/blue/menu.css" /> </head> <body style="margin: 0"> <div id="Menu"> <ul id="MenuUl"> <!--一級菜單 --> <s:iterator value="#application.topPrivilegeList"> <li class="level1"> <div onClick="menuClick(this)" class="level1Style"> <img src="style/images/MenuIcon/FUNC20001.gif" class="Icon" />${name } </div> <ul style="display: none;" class="MenuLevel2"> <!-- 二級菜單 --> <s:iterator value="children"> <li class="level2"> <div class="level2Style"> <img src="style/images/MenuIcon/menu_arrow_single.gif" /> ${name } </div> </li> </s:iterator> </ul> </s:iterator> </ul> </div> </body> </html> |
可是改爲這樣以後卻會報懶加載異常:
懶加載:對象被加載的時候,其屬性不會被加載,只有在真正須要這些資源的時候纔會加載。其思想簡單來講就是拖到最後一刻,直到萬不得已才加載,纔開始佔用資源。
爲何在web.xml中加上了解決懶加載問題的代碼,卻仍是會出現懶加載問題?
<!-- 配置Spring的OpenSessionInViewFilter以解決懶加載異常的問題 --> <filter> <filter-name>OpenSessionInViewFilter</filter-name> <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class> </filter> <filter-mapping> <filter-name>OpenSessionInViewFilter</filter-name> <url-pattern>*.do</url-pattern> </filter-mapping> |
由於web.xml中的這段代碼解決的只是同一個請求的懶加載問題,當有多個請求出現懶加載問題時,它是解決不了的.
詳解:
在此正是多個請求致使的懶加載異常,啓動應用程序時topPrivilegeList就被加載了(Listener的特性),過兩個小時以後再作第一次訪問(第一個請求?),左側菜單爲一個請求,這時就要用到topPrivilegeList的懶加載屬性(菜單中的二級菜單:children屬性),而他們根本不是同一個請求,因此會出現懶加載異常.解決方案:直接關掉懶加載.
Privilege.hbm.xml:
<!-- children屬性,表達的是本對象與Privilege(children)的一對多關係 --> <set name="children" order-by="id ASC" lazy="false"> <key column="parentId"></key> <one-to-many class="Privilege"/> </set> |
同理,登陸時(第一個請求),會將user放到session中,而加載user對象時卻沒有加載user下的roles(懶加載),因此當要用到user下的全部roles時(第二個請求),又會出現懶加載問題.Roles下的privileges也同樣,因此都要改.
User.hbm.xml:
<!-- roles屬性,表達的是本對象與Role的多對多關係 --> <set name="roles" table="itcast_user_role" lazy="false"> <key column="userId"></key> <many-to-many class="Role" column="roleId"></many-to-many> </set> |
Role.hbm.xml:
<!-- privileges屬性,表達的是本對象與Privilege的多對多關係 --> <set name="privileges" table="itcast_role_privilege" lazy="false"> <key column="roleId"></key> <many-to-many class="Privilege" column="privilegeId"></many-to-many> </set> |
修改完成以後的效果圖:
由於left.jsp中遍歷二級菜單時有這樣一行代碼style="display: none;",這就致使左側菜單默認不顯示二級菜單,將這行代碼刪去以後,就會默認顯示全部菜單.
1.給左側菜單添加超連接.由於url已經被存進數據庫中
因此能夠這麼寫:
<a href="${pageContext.request.contextPath }${url}.do" target="right">${name }</a>
2.給一級菜單更換圖片:
能夠把要用到的圖片複製一份,將副本名字改爲數據庫中權限(即菜單)的id值:
而後導入圖片的代碼改爲這樣:
<img src="style/images/MenuIcon/${id }.gif" class="Icon" />
3.當點擊一級菜單時,會顯示他全部的二級菜單,再點擊一下就會關閉他全部的二級菜單:
onClick=" $(this).next().toggle() "
此時left.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <html> <head> <title>導航菜單</title> <%@ include file="/WEB-INF/jsp/public/header.jspf"%> <link type="text/css" rel="stylesheet" href="style/blue/menu.css" /> </head> <body style="margin: 0"> <div id="Menu"> <ul id="MenuUl"> <!-- 一級菜單 --> <s:iterator value="#application.topPrivilegeList"> <li class="level1"> <div onClick=" $(this).next().toggle() " class="level1Style"> <img src="style/images/MenuIcon/${id }.gif" class="Icon" /> ${name } </div> <ul class="MenuLevel2"> <!-- 二級菜單 --> <s:iterator value="children"> <li class="level2"> <div class="level2Style"> <img src="style/images/MenuIcon/menu_arrow_single.gif" /> <a href="${pageContext.request.contextPath }${url}.do" target="right">${name }</a> </div> </li> </s:iterator> </ul> </s:iterator> </ul> </div> </body> </html> |
Tips: target="right"將頁面指定在name="right"的地方顯示(即右側)
思路:能夠在left.jsp遍歷topPrivilegeList的同時,取出當前存在session中的user(正在登陸的用戶),判斷user是否有當前正在遍歷的權限,有則顯示權限,沒有則進行下一次遍歷,直到將topPrivilegeList遍歷完成.
Left.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <html> <head> <title>導航菜單</title> <%@ include file="/WEB-INF/jsp/public/header.jspf"%> <link type="text/css" rel="stylesheet" href="style/blue/menu.css" /> </head> <body style="margin: 0"> <div id="Menu"> <ul id="MenuUl"> <!-- 一級菜單 --> <s:iterator value="#application.topPrivilegeList"> <s:if test=" #session.user.hasPrivilegeByName(name) "> <li class="level1"> <div onClick=" $(this).next().toggle() " class="level1Style"> <img src="style/images/MenuIcon/${id }.gif" class="Icon" /> ${name } </div> <ul class="MenuLevel2"> <!-- 二級菜單 --> <s:iterator value="children"> <s:if test=" #session.user.hasPrivilegeByName(name) "> <li class="level2"> <div class="level2Style"> <img src="style/images/MenuIcon/menu_arrow_single.gif" /> <a href="${pageContext.request.contextPath }${url}.do" target="right">${name }</a> </div> </li> </s:if> </s:iterator> </ul> </li> </s:if> </s:iterator> </ul> </div> </body> </html> |
Tips:<s:if test=" #session.user.hasPrivilegeByName(name) ">
hasPrivilegeByName()是user中的方法,參數name是當前正在遍歷的權限的名字.
Ognl表達式中能夠調用方法(而el表達式不能).
User.java中得新增方法:
/** * 判斷是否有權限 * @param priviName 權限的名稱 */ public boolean hasPrivilegeByName(String privName){ for (Role role : roles) { for (Privilege p : role.getPrivileges()) { if(p.getName().equals(privName)){ return true; } } } return false; } |
然而,測試運行卻會報錯,由於當前沒有登陸用戶.
這個target是指擁有當前方法的對象實例,在此爲user.
當登陸admin後,卻什麼也看不到.
這是由於admin是特殊用戶,沒有與任何崗位關聯.
因此這時還得繼續判斷是否爲admin.
User.java:
/** * 判斷是否有權限 * @param priviName 權限的名稱 */ public boolean hasPrivilegeByName(String privName){ //若是是超級管理員,就有全部權限 if(isAdmin()){ return true; }
for (Role role : roles) { for (Privilege p : role.getPrivileges()) { if(p.getName().equals(privName)){ return true; } } } return false; }
/* * 判斷當前用戶是否爲admin */ public boolean isAdmin(){ return "admin".equals(loginName); } |
Tips:用equals()進行比較時,通常把必定不爲null的值放在左邊.由於:(不爲nul)l.equals(任何值(包括null))其返回值都不爲空,此時就算右邊爲空,他也會返回false.而若是(null).equals(任何值(包括不爲null的值))會拋空指針異常.
打個比方,若是程序員沒有刪除用戶的權限,那麼身爲程序員的用戶在用戶列表頁面,要麼看到的"用戶刪除"連接是灰色的,要麼根本看不到"用戶刪除"連接.在此用的是第二種方式.有一下幾種方案:
方案1太麻煩,不是用struts標籤,而是用其餘標籤(如原始標籤:<a/>)時適合用方案三,在此咱們選擇用方案四.
首先要找到<s:a/>所在源碼:隨便找到個<s:a/>標籤,按住ctrl,左鍵單擊,就進入了這裏:
由此得知該標籤所在類爲:
<tag-class>org.apache.struts2.views.jsp.ui.AnchorTag</tag-class> |
複製類名,按住CTRL + SHIFT + T :跳轉到該類.可是該類是隻讀形式.
修改方法:新建一個包,包名和該類所在包的包名相同,建立一個類,類名和該類同樣,全選複製該類內容,粘貼過來,再修改相應方法.
原理:1.雖然這兩個類包名和類名都同樣,但他們在不一樣jar包裏,全部這是可行的.2.啓動應用程序的時候,全部jar包都會被加載而後被放到緩存中,以後就不會再被加載了,重點是,哪一個類先被加載,哪一個類就會被使用.項目中,它都是先加載咱們本身的類,找不到的話纔會去加載lib中的類.
用繼承的方式不太好,由於萬一該類是final修飾的,那就不可繼承,又或者咱們要修改的方法是private修飾的,那也不能繼承.
找該類所在包名的方法:
具體修改:
AnchorTag.java:
@Override public int doEndTag() throws JspException { //獲取當前登陸的用戶 User user = (User) pageContext.getSession().getAttribute("user"); if(user == null){ throw new RuntimeException("當前沒有登錄用戶!"); }
//獲取所須要的權限url(在action屬性值中,但須要處理一下) String privUrl = "/" + action;
//a.去掉後面的參數字符串(若是有) int pro = privUrl.indexOf("?"); if(pro > -1){ privUrl = privUrl.substring(0, pro); } //b.去掉後面的UI後綴(若是有) if(privUrl.endsWith("UI")){ privUrl = privUrl.substring(0, privUrl.length()-2); }
//根據權限決定是否顯示超連接 if(user.hasPrivilegeByUrl(privUrl)){ return super.doEndTag(); //輸出<a/>標籤,並繼續執行此標籤後面的jsp代碼 }else{ return BodyTagSupport.EVAL_PAGE; //不輸出<a/>,並繼續執行此標籤後面的jsp代碼 } } |
2. BodyTagSupport.EVAL_PAGE; //不輸出<a/>,並繼續執行此標籤後面的jsp代碼.若是沒有權限,就不輸出<a/>,也就看不到超連接了;
3.在這個類中,Struts框架對action進行了定義,頁面上的超連接地址
就是保存到了action中,因此在這裏能夠直接String privUrl = "/" + action;加"/"是由於數據庫中的url地址
4.不肯定privUrl的截取是否正確的話,能夠在 if(pro > -1){ 這行打斷點.首先登陸用戶,而後它會自動跳到以下界面:
點擊一下頁面上的用戶管理(即讓程序進入對privUrl的判斷),對String privUrl = "/" + action;中的privUrl右鍵單擊,選擇"Watch",會出現以下窗口:
而後屢次單擊
進入下一行代碼,屢次觀察Expressions窗口中的privUrl的值是否正確.此時再點擊
進入下一次判斷,再點擊…進入下一行代碼,觀察…如此反覆操做.
前面都加了"/";
此時Uer.java再新增一個方法(經過url來判斷是否有權限):
/** * 判斷是否有權限 * @param priviUrl 權限的url */ public boolean hasPrivilegeByUrl(String privUrl) { //若是是超級管理員,就有全部的權限 if(isAdmin()){ return true; } //若是是通常用戶,就得判斷是否有權限 for (Role role : roles) { for (Privilege p : role.getPrivileges()) { if(privUrl.equals(p.getUrl())){ return true; } } } return false; } |
Tips: privUrl.equals(p.getUrl())將privUrl放在前面是由於p.getUrl有可能爲null.如果頂級菜單就沒有url.
之後還可能須要經過判斷url來判斷是否有權限,那麼就能夠將AnchorTag.java中對url的判斷語句放到user.java裏,修改後
AnchorTag.java:
@Override public int doEndTag() throws JspException { //獲取當前登陸的用戶 User user = (User) pageContext.getSession().getAttribute("user"); if(user == null){ throw new RuntimeException("當前沒有登錄用戶!"); }
//獲取所須要的權限url(在action屬性值中,但須要處理一下) String privUrl = "/" + action;
//根據權限決定是否顯示超連接 if(user.hasPrivilegeByUrl(privUrl)){ return super.doEndTag(); //輸出<a/>標籤,並繼續執行此標籤後面的jsp代碼 }else{ return BodyTagSupport.EVAL_PAGE; //不輸出<a/>,並繼續執行此標籤後面的jsp代碼 } } |
User.java:
/** * 判斷是否有權限 * @param priviUrl 權限的url */ public boolean hasPrivilegeByUrl(String privUrl) { //若是是超級管理員,就有全部的權限 if(isAdmin()){ return true; }
//a.去掉後面的參數字符串(若是有) int pro = privUrl.indexOf("?"); if(pro > -1){ privUrl = privUrl.substring(0, pro); } //b.去掉後面的UI後綴(若是有) if(privUrl.endsWith("UI")){ privUrl = privUrl.substring(0, privUrl.length()-2); }
//若是是通常用戶,就得判斷是否有權限 for (Role role : roles) { for (Privilege p : role.getPrivileges()) { if(privUrl.equals(p.getUrl())){ return true; } } } return false; }
/* * 判斷當前用戶是否爲admin */ public boolean isAdmin(){ return "admin".equals(loginName); } |
做用:避免用戶在沒有登陸的狀況下,卻能經過直接輸入url的方式來進行某些操做.好比沒有登陸時輸入http://localhost:8080/ItcastOA/user_list.do來查看用戶列表,這是不容許的,必須得先登陸,而後再判斷用戶是否具備進行該操做的權限.
(這集沒聲音,光知道操做,理解不深,因此謀得解釋啊啊啊啊啊)
先在util包中建立一個類
CheckPrivilegeInterceptor:
package cn.itcast.oa.util;
import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
public class CheckPrivilegeInterceptor extends AbstractInterceptor {
@Override public String intercept(ActionInvocation invocation) throws Exception { System.out.println("======>攔截器(前)<======"); String result = invocation.invoke(); // 放行 System.out.println("======>攔截器(後)<======"); return result; } } |
接着是到struts.xml中進行配置:
<interceptors> <!-- 聲明攔截器 --> <interceptor name="CheckPrivilege" class="cn.itcast.oa.util.CheckPrivilegeInterceptor"></interceptor> <!-- 配置咱們本身的攔截器棧 --> <interceptor-stack name="myDefaultStack"> <interceptor-ref name="CheckPrivilege"></interceptor-ref> <interceptor-ref name="defaultStack"></interceptor-ref> </interceptor-stack> </interceptors> <!-- 配置默認的攔截器棧 --> <default-interceptor-ref name="myDefaultStack"></default-interceptor-ref> |
必定要引入默認攔截器.這段配置代碼應該是要放到前面的.
CheckPrivilegeInterceptor:
package cn.itcast.oa.util;
import cn.itcast.oa.domain.User;
import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
public class CheckPrivilegeInterceptor extends AbstractInterceptor {
@Override public String intercept(ActionInvocation invocation) throws Exception {
//準備數據 //a.獲取當前登陸的用戶 User user = (User) ActionContext.getContext().getSession().get("user"); //b.獲取當前訪問的url String namespace = invocation.getProxy().getNamespace(); String actionName = invocation.getProxy().getActionName(); if(null == namespace || "".equals(namespace)){ namespace = "/"; } if(!namespace.endsWith("/")){ namespace += "/"; } String url = namespace + actionName;
//一,若是用戶未登陸,則轉到登陸頁面 if(user == null){ //a.若是當前訪問的是登陸頁面:loginout_loginUI,loginout_login,則放行 if(url.startsWith("/loginout_login")){ return invocation.invoke();
//b.若是訪問的不是登陸登陸功能,就轉到登陸頁面 }else{ return "loginUI"; }
//二,若是用戶已登陸,則判斷權限 }else{ //a.若是有權限訪問當前url,則放行 if(user.hasPrivilegeByUrl(url)){ return invocation.invoke();
//b.若是沒有權限訪問當前url,則轉到提示消息的頁面 }else{ return "noPrivilegeUI"; } } } } |
建立noPrivilegeUI.jsp(做用:提示沒有權限),並套用靜態頁面:
noPrivilegeUI.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"%> <HTML> <HEAD> <TITLE>沒有權限</TITLE> <%@ include file="/WEB-INF/jsp/public/header.jspf" %> <SCRIPT TYPE="text/javascript"> </SCRIPT> </HEAD> <BODY>
<DIV ID="Title_bar"> <DIV ID="Title_bar_Head"> <DIV ID="Title_Head"></DIV> <DIV ID="Title"><!--頁面標題--> <IMG BORDER="0" WIDTH="13" HEIGHT="13" SRC="${pageContext.request.contextPath}/style/images/title_arrow.gif"/>提示 </DIV> <DIV ID="Title_End"></DIV> </DIV> </DIV>
<!--顯示錶單內容--> <DIV ID="MainArea"> <DIV CLASS="ItemBlock_Title1"> </DIV>
<DIV CLASS="ItemBlockBorder" STYLE="margin-left: 15px;"> <DIV CLASS="ItemBlock" STYLE="text-align: center; font-size: 16px;"> 出錯了,您沒有權限訪問此功能! </DIV> </DIV>
<!-- 操做 --> <DIV ID="InputDetailBar"> <A HREF="javascript:history.go(-1);"><IMG SRC="${pageContext.request.contextPath}/style/images/goBack.png"/></A> </DIV>
</DIV>
</BODY> </HTML> |
在struts.xml中配置全局變量(攔截器中的返回值和action中的返回值效果是同樣的):
<!-- 全局配置 --> <global-results> <result name="loginUI">/WEB-INF/jsp/loginoutAction/loginUI.jsp</result> <result name="noPrivilegeUI">/noPrivilegeUI.jsp</result> </global-results> |
到此結束的話,系統會存在一個很大的漏洞:由於還有不少登陸用戶均可以訪問的url(也就是用戶登陸以後,不須要再進行控制的功能(如首頁,註銷))沒被存進數據庫中.這就致使用戶被系統誤判爲是沒有權限!!
思路:將全部權限的url查詢出來放到一個集合中,若是是超級管理員,則它有全部權限(返回true).若是當前要訪問的權限url不在權限url集合中,返回true(表明只要是登陸用戶均可以訪問);若是當前權限url在權限url集合中,則要繼續判斷當前用戶是否具備該權限.
因爲權限url是固定的(從初始化權限數據開始就固定下來了),全部能夠利用監聽器的思想.在啓動應用程序,全部請求到來以前,就將url集合放入最大做用域application中(這樣就只須要查詢一次,之後都不用再查了).
User.java:
/** * 判斷是否有權限 * @param priviUrl 權限的url */ public boolean hasPrivilegeByUrl(String privUrl) { //若是是超級管理員,就有全部的權限 if(isAdmin()){ return true; }
//a.去掉後面的參數字符串(若是有) int pro = privUrl.indexOf("?"); if(pro > -1){ privUrl = privUrl.substring(0, pro); } //b.去掉後面的UI後綴(若是有) if(privUrl.endsWith("UI")){ privUrl = privUrl.substring(0, privUrl.length()-2); }
//若是是通常用戶,就得判斷是否有權限 //a.若是這個url是不須要控制的功能(登陸後就能夠直接使用的,如首頁,註銷),這時應該直接返回ture Collection<String> allPrivilegeUrls = (Collection<String>) ActionContext.getContext().getApplication().get("allPrivilegeUrls"); if(!allPrivilegeUrls.contains(privUrl)){ return true;
//b.若是這個url是須要控制的功能(登陸還得有對應權限才能使用),這時應該判斷權限 }else{ for (Role role : roles) { for (Privilege p : role.getPrivileges()) { if(privUrl.equals(p.getUrl())){ return true; } } } return false; } } /* * 判斷當前用戶是否爲admin */ public boolean isAdmin(){ return "admin".equals(loginName); } |
OAInitListener.java:
package cn.itcast.oa.util;
import java.util.List;
import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener;
import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils;
import cn.itcast.oa.domain.Privilege; import cn.itcast.oa.service.PrivilegeService;
import com.opensymphony.xwork2.ActionContext; @SuppressWarnings("unused") public class OAInitListener implements ServletContextListener {
private Log log =LogFactory.getLog(OAInitListener.class);
//初始化 public void contextInitialized(ServletContextEvent sce) { ServletContext application = sce.getServletContext();
//從spring的容器中取出privilegeService的對象實例 WebApplicationContext ac = WebApplicationContextUtils.getWebApplicationContext(application); PrivilegeService privilegeService = (PrivilegeService) ac.getBean("privilegeServiceImpl");
//查詢全部頂級的權限列表,並放到application做用域中 List<Privilege> topPrivilegeList = privilegeService.findtopPrivilegeList(); application.setAttribute("topPrivilegeList", topPrivilegeList); log.info("======= topPrivilegeList已經放到application做用域中了! =======");
//查詢全部權限列表,並放到application做用域中 List<String> allPrivilegeUrls = privilegeService.getAllPrivilegeUrls(); application.setAttribute("allPrivilegeUrls", allPrivilegeUrls); log.info("======= allPrivilegeUrls已經放到application做用域中了! =======");
}
//銷燬 public void contextDestroyed(ServletContextEvent sce) { }
} |
privilegeServiceImpl:
/** * 查詢全部權限的url */ public List<String> getAllPrivilegeUrls() { return getSession().createQuery(// "select distinct p.url from Privilege p where p.url is not null")// .list(); } |
查出來的url要避免空值和重複的值,由於數據庫中就有空值和重複的值:
1).用戶登陸後,在Tomcat重啓後,還應是登陸狀態.
原理: tomcat在(正常)關閉的時候,會把內存中的session序列化到本地硬盤上,再啓動服務器時,會把反序列化拿到硬盤中還原session信息.
演示:
(此時服務器開着)正常關閉服務器,會看到session被序列化到了本地硬盤上:
再啓動服務器,session的序列化信息又會被還原到內存中:
前提:User.java和它所關聯的全部實體都實現序列化: implements Serializable.
效果:
打開服務器,以超級管理員的身份登陸系統:
此時右鍵TomcatàRestart:
刷新頁面,本來系統會跳到登陸頁面,要求從新登陸,可實現序列化後,session中的user還在,因此用戶仍是保持着登陸狀態,不須要再從新登陸了:
2)頁面嵌套的問題.
演示問題:
第一種方法是啓動服務器,登陸用戶,右鍵退出按鈕,單擊"在新窗口中打開連接"
在新窗口中刷新一次.(即退出了當前用戶)
第二種方法是直接等待session過時(期間不要進行任何操做),session默認過時時間是半小時.
登陸後也還有頁面嵌套的問題:
此時手動刷新一下,頁面就會恢復正常了.
咱們要作的就是避免頁面嵌套的問題,應在loginUI.jsp中加入自動刷新的代碼:
<script type="text/javascript"> if(window.parent != window){ window.parent.location.href = window.location.href; } </script> |
Window指的就是整個頁面(整個大框),因此window是沒有上級的,它的parent就是它本身.當window.parent != window,說明出現了頁面嵌套的狀況,這時頁面會自動刷新window.parent.location.href = window.location.href;
1.顯示左側菜單須要在left.jsp中進行兩次遍歷(第一次是TopPrivilegeList,第二次是children.其中TopPrivilegeList是利用監聽器放到最大做用域application中的), 在兩次遍歷的同時還分別調用了User中的hasPrivilegeByName()來判斷是否有權限;
2.顯示右側超連接是用了修改jar源碼的方式,還調用了User中的hasPrivilegeByUrl()來判斷是否有權限;
3.攔截每一次請求是專門寫了一個攔截器,其中也調用了User中的hasPrilegeByUrl()來判斷是否有權限.攔截器的思路:用戶未登陸的時候有兩種狀況:要是當前要訪問的url是登陸頁面的話,就放行,不然就跳轉到登陸頁面.用戶已登錄的時候,也有兩種狀況:要是沒有當前訪問的url權限,則提示沒有權限,要是有權限就放行.
而由於有不少登陸用戶都能訪問的url沒有被存進數據庫中(好比首頁,註銷),因此當用戶訪問這些url時也會被認爲是沒有權限而沒法訪問.這是咱們就應該修改User中的hasPrilegeByUrl().hasPrilegeByUrl()的思路是:如果超級管理員則有全部權限(返回true),處理被傳遞過來的url,對url進行判斷.如果當前要訪問的url不在全部權限url集合中,則返回true.(這說明全部登陸用戶都能訪問這個url).不然就要進一步判斷用戶是否有權限了.至於url集合:由於權限url是固定的,因此它也能夠利用監聽器存到最大做用域application中(好處是查一次就行了,查過以後它會被放入緩存中,之後用的時候就直接從緩存中拿,而不用查第二次了).
當這個雙箭頭被選中時,右邊打開任何一個文件,在左邊都會實時顯示該文件所在的位置.如:
快捷鍵:CTRL+SHIFT+T 查找全部java文件,按回車後會跳到那個頁面.好比按CTRL+SHIFT+T後打入UserService,按回車以後就跳到了UserService頁面.
按home,光標會跳到當前行的行首,按end,光標會跳到當前行的行末.