day53_电力系统_ztree动态生成&权限控制

来源:互联网 发布:邬桑为什么哭 知乎 编辑:程序博客网 时间:2024/06/06 07:05

项目第六天(系统登录)

1:struts2的validator校验(后台校验)

项目中进行数据校验的方式:

Js校验(前台校验)
Ajax校验(后台校验)
Struts2的validator校验(后台校验)

项目开发的时候,针对需求:提供校验机制,项目经理要求,即做前台校验又做后台校验(保证数据安全),同时由于校验,查询性能也相应降低。
所以要求:

如果数据不是很重要,可以只做前台校验
如果数据很重要,即做前台校验又要做后台校验

第一步:在Action中定义:如果出现校验问题,使用:

if(elecUser==null){        this.addFieldError("error", "用户名输入有误!");        return "error";}


二步:在struts.xml中定义:

<action name="elecMenuAction_*" class="elecMenuAction" method="{1}">    <result name="error">/WEB-INF/page/menu/index.jsp</result></action>

第三步:在menu/index.jsp中,定义:
使用struts2的标签,输出错误信息:

<s:fielderror/>

第四步:效果:
这里写图片描述

第五步:修改错误的样式:字体变红;去掉前面的圆圈
改变错误的样式
<s:fielderror>标签的封装在:struts2的核心包下
这里写图片描述

只需要在项目的src下添加2个文件夹template/simple,将fielderror.ftl的文件放置到该文件夹下,此时启动的时候,就会覆盖struts核心包的下加载的内容
修改fielderror.ftl中的内容:

<#list eKeys as eKey><#t/>     <#assign eValue = fieldErrors[eKey]><#t/>     <#list eValue as eEachValue><#t/>         <font color='red'><span><#if parameters.escape>${eEachValue!?html}<#else>${eEachValue!}</#if></span></font>     </#list><#t/></#list><#t/>

2:hibernate的懒加载问题

产生:
当使用hibernate查询一个对象的时候,如果Session关闭,再调用该对象关联的集合或者对象的时候,会产生懒加载异常!
这里写图片描述

解决方案:

方案一:
在Session关闭之前,查询对象关联的集合或者对象,所有在业务层的方法上添加:

public ElecUser findUserByLogonName(String name) {        String condition = " and o.logonName = ?";        Object [] params = {name};        List<ElecUser> list = elecUserDao.findCollectionByConditionNoPage(condition, params, null);        //数据库表中存在该用户,返回ElecUser对象        ElecUser elecUser = null;        if(list!=null && list.size()>0){            elecUser = list.get(0);        }        /***         * 解决懒加载异常        除了OID之外的其他属性         */        elecUser.getElecRoles().size();        return elecUser;    }

方案二:在Service层的方法中(Session关闭之前),初始化对象关联的集合或者对象

public ElecUser findUserByLogonName(String name) {        String condition = " and o.logonName = ?";        Object [] params = {name};        List<ElecUser> list = elecUserDao.findCollectionByConditionNoPage(condition, params, null);        //数据库表中存在该用户,返回ElecUser对象        ElecUser elecUser = null;        if(list!=null && list.size()>0){            elecUser = list.get(0);        }        /***         * 解决懒加载异常         */        Hibernate.initialize(elecUser.getElecRoles());        return elecUser;    }

方案三:在ElecUser.hbm.xml中,添加lazy=”false”,查询用户的同时,立即检索查询用户关联的角色集合:

<set name="elecRoles" table="elec_user_role" inverse="true" lazy="false">    <key>        <column name="userID"></column>    </key>    <many-to-many class="cn.itcast.elec.domain.ElecRole" column="roleID"/></set>

表示查询用户的时候,立即检索用户所关联的角色
建议项目开发中不要在.hbm.xml中添加过多的lazy=false,这样如果表关联比较多,不需要查询的对象也被加载了,性能会出现问题。

方案四:使用spring提供的过滤器OpenSessionInViewFilter,在web容器中添加该过滤器

在web.xml中添加:
要求:该过滤器一定要放置到strtus2的过滤器的前面,先执行该过滤器。

<!-- 添加spring提供的过滤器,解决hibernate的懒加载问题 -->    <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>        <url-pattern>*.jsp</url-pattern>    </filter-mapping>    <!-- 配置struts2的过滤器,这是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>*.do</url-pattern>        <url-pattern>*.jsp</url-pattern>    </filter-mapping>

表示:OpenSessionInViewFilter过滤器实现的原理:

1:事务提交:spring提供的声明式事务控制,仍然在业务层的方法进行处理,方法执行完毕后,事务会自动提交,如果出现异常,事务就会回滚。但是它延迟了Session关闭的时间。
2:Session关闭:Session在页面上进行关闭,此时当页面上的数据加载完成之后,再关闭Session。

问题:如果你开发的系统对页面数据加载比较大的时候,不适合使用
OpenSessionInViewFilter,这样Session不能及时关闭,另一个Session就无法访问,连接不够使用,就会产生“假死”现象。

3:验证码

这里写图片描述
作用:防止恶意的测试系统的用户名和密码(利用循环输入用户名和密码测试),采用验证码,每次到登录页面的时候,验证码的值是不同的,需要重新输入。

第一步:index.jsp页面:

<tr>    <td width="100"><img border="0" src="${pageContext.request.contextPath}/images/check.jpg" width="75" height="20"></td>    <td>        <table>            <tr>                <td>                    <input type="text" name="checkNumber" id="checkNumber" value=""  maxlength="4" size="7">                </td>                <td>                    <img src="${pageContext.request.contextPath}/image.jsp" name="imageNumber" id="imageNumber" style="cursor:hand" title="点击可更换图片" height="20" onclick="checkNumberImage()"/>                </td>            </tr>        </table>    </td></tr>

添加:
Js方法

function checkNumberImage(){    var imageNumber = document.getElementById("imageNumber");    imageNumber.src = "${pageContext.request.contextPath}/image.jsp?timestamp="+new Date().getTime();}

image.jsp(生成4位随机数字验证码)

Random random = new Random();    String sRand = "";    for (int i = 0; i < 4; i++) {        String rand = String.valueOf(random.nextInt(10));        sRand += rand;    }    // 将认证码存入SESSION    session.setAttribute("CHECK_NUMBER_KEY", sRand);// 输出图象到页面    try {        ImageIO.write(image, "JPEG", response.getOutputStream());    } catch (Exception e) {    }

第二步:在Action中进行校验,创建LoginUtils类

public class LogonUtils {    /**验证验证码输入是否正确*/    public static boolean checkNumber(HttpServletRequest request) {        //从页面中获取输入框的值        String checkNumber = request.getParameter("checkNumber");        if(StringUtils.isBlank(checkNumber)){            return false;        }        //从Session中获取验证码的值        String CHECK_NUMBER_KEY = (String)request.getSession().getAttribute("CHECK_NUMBER_KEY");        if(StringUtils.isBlank(CHECK_NUMBER_KEY)){            return false;        }        return checkNumber.equalsIgnoreCase(CHECK_NUMBER_KEY);    }}

4:记住我

这里写图片描述
作用:记住当前用户名和密码,下次登录名不需要用户再次输入

第一步:index.sp页面

<tr>    <td width="100"><img border="0" src="${pageContext.request.contextPath}/images/remeber.jpg" width="75" height="20"></td>    <td>        <input type="checkbox" name="remeberMe" id="remeberMe" value="yes"/>    </td></tr>

第二步:Action代码的处理,创建LoginUtils类

public class LogonUtils {    /**记住我功能*/    public static void remeberMe(String name, String password,            HttpServletRequest request, HttpServletResponse response) {        //1:创建2个Cookie,存放指定值        Cookie nameCookie = new Cookie("name",name);        Cookie passwordCookie = new Cookie("password",password);        //2:设置Cookie的有效路径(指定当前项目)        nameCookie.setPath(request.getContextPath()+"/");        passwordCookie.setPath(request.getContextPath()+"/");        //3:设置Cookie的有效时间(1周)        //获取页面复选框的值(用作判断)        String remeberMe = request.getParameter("remeberMe");        //此时表示复选框选中        if(remeberMe!=null && remeberMe.equals("yes")){            nameCookie.setMaxAge(7*24*60*60);            passwordCookie.setMaxAge(7*24*60*60);        }        //此时表示复选框没有被选中        else{            nameCookie.setMaxAge(0);            passwordCookie.setMaxAge(0);        }        //4:将Cookie存放到response中        response.addCookie(nameCookie);        response.addCookie(passwordCookie);    }}

第三步:在index.jsp页面中读取Cookie中的数据,jsp中嵌套java代码

<%String name = "";String password = "";String checked = "";Cookie [] cookies = request.getCookies();if(cookies!=null && cookies.length>0){    for(Cookie cookie:cookies){        if(cookie.getName().equals("name")){            name = cookie.getValue();            checked = "checked";        }        if(cookie.getName().equals("password")){            password = cookie.getValue();        }    }}%>

缺点:将java代码放置到jsp上,要求jsp先要执行编译java代码,然后再执行。效率会降低,能否将java代码抽取出去呢?
分析:在跳转到index.jsp页面之前先从Cookie中获取数据,放置到HttpRequest对象中进行显示,这样可以使用过滤器(filter)完成:

第四步:添加过滤器

public class SystemFilter implements Filter {    /**web容器启动的时候,执行的方法*/    public void init(FilterConfig config) throws ServletException {    }    /**每次访问URL连接的时候,先执行过滤器的doFilter的方法*/    public void doFilter(ServletRequest req, ServletResponse res,            FilterChain chain) throws IOException, ServletException {        HttpServletRequest request = (HttpServletRequest) req;        HttpServletResponse response = (HttpServletResponse) res;        //获取访问的连接地址        String path = request.getServletPath();        //在访问首页index.jsp页面之前,先从Cookie中获取name,password的值,并显示在页面上(完成记住我)        this.forwordIndexPage(path,request);        //放行        chain.doFilter(request, response);    }    /**销毁*/    public void destroy() {    }    /**在访问首页index.jsp页面之前,先从Cookie中获取name,password的值,并显示在页面上(完成记住我)*/    private void forwordIndexPage(String path, HttpServletRequest request) {        if(path!=null && path.equals("/index.jsp")){            String name = "";            String password = "";            String checked = "";            Cookie [] cookies = request.getCookies();            if(cookies!=null && cookies.length>0){                for(Cookie cookie:cookies){                    if(cookie.getName().equals("name")){                        name = cookie.getValue();                        /**                         * 如果name出现中文,对中文进行解码                         */                        try {                            name = URLDecoder.decode(name, "UTF-8");                        } catch (UnsupportedEncodingException e) {                            e.printStackTrace();                        }                        checked = "checked";                    }                    if(cookie.getName().equals("password")){                        password = cookie.getValue();                    }                }            }            request.setAttribute("name", name);            request.setAttribute("password", password);            request.setAttribute("checked", checked);        }    }}

第五步:在web.xml中添加:

<!-- 自定义过滤器,要求添加到struts2过滤器的前面 --><filter>    <filter-name>SystemFilter</filter-name>      <filter-class>cn.itcast.elec.util.SystemFilter</filter-class></filter><filter-mapping>    <filter-name>SystemFilter</filter-name>    <url-pattern>*.do</url-pattern>    <url-pattern>*.jsp</url-pattern></filter-mapping>测试后:发现问题:如果name中存在中文,此时中文字符是不能存放到Cookie对象中,使用HttpResponse对象添加Cookie会抛出异常。解决方案:使用URLEncode类和URLDecode类进行编码和解码在LogonUtils类对name进行编码:try {    name = URLEncoder.encode(name, "UTF-8");} catch (UnsupportedEncodingException e) {    e.printStackTrace();}//1:创建2个Cookie,存放指定值Cookie nameCookie = new Cookie("name",name);Cookie passwordCookie = new Cookie("password",password);在过滤器SystemFilter类对name进行解码:if(cookie.getName().equals("name")){    name = cookie.getValue();    /**    * 如果name出现中文,对中文进行解码    */    try {        name = URLDecoder.decode(name, "UTF-8");    } catch (UnsupportedEncodingException e) {        e.printStackTrace();    }    checked = "checked";}

5:jquery的ztree插件的使用(完成动态加载树型结构)

第一步:在left.jsp中

<script language="JavaScript" src="${pageContext.request.contextPath }/script/jquery-1.4.2.js"></script><script language="JavaScript" src="${pageContext.request.contextPath }/script/jquery-ztree-2.5.js"></script><script language="JavaScript" src="${pageContext.request.contextPath }/script/treeMenu.js"></script><link type="text/css" rel="stylesheet" href="${pageContext.request.contextPath }/css/menu.css" /><link rel="stylesheet" href="${pageContext.request.contextPath }/css/zTreeStyle/zTreeStyle.css" type="text/css">Left.jsp中使用<ul><TABLE border=0 width="20">    <TR>        <TD width=340px align=center valign=top>        <div class="zTreeDemoBackground">            <ul id="menuTree" class="tree" ></ul>        </div>              </TD>    </TR></TABLE>

第二步:在treeMenu.js中定义:

var menu = {    setting: {        isSimpleData: true,        treeNodeKey: "mid",        treeNodeParentKey: "pid",        showLine: true,        root: {            isRoot: true,            nodes: []        }    },    loadMenuTree:function(){        $.post("elecMenuAction_showMenu.do",{},function(data){            $("#menuTree").zTree(menu.setting, data);        });    }};$().ready(function(){    menu.loadMenuTree();});

第三步:在Action中添加:

public String showMenu(){    //获取Session中存放的权限字符串(格式:aa@ab@ac)    String popedom = (String) request.getSession().getAttribute("globle_popedom");    //1:查询当前用户所具有的功能权限,使用权限,查询权限表,返回List<ElecPopedom>    List<ElecPopedom> list = elecRoleService.findPopedomListByUser(popedom);    //2:将list放置到栈顶,栈顶的对象转换成json数组的形式    ValueStackUtils.setValueStack(list);    return "showMenu";}

第四步:(hql语句嵌套查询),Service类定义:

public List<ElecPopedom> findPopedomListByUser(String popedom) {    //hql语句和sql语句的嵌套查询String condition = " and o.mid IN('"+popedom.replace("@", "','")+"') AND isMenu = ?";    Object [] params = {true};    Map<String, String> orderby = new LinkedHashMap<String, String>();    orderby.put("o.mid", "asc");    List<ElecPopedom> list = elecPopedomDao.findCollectionByConditionNoPage(condition, params, orderby);    return list;}

第五步:在struts.xml中添加:

<!-- 将集合压入到栈顶,集合返回页面的时候,转换成json的形式 --><result name="showMenu" type="json"></result>

6:自定义标签

使用当前用户具有的权限,控制页面上的按钮或者链接是否可见。
详情请见技术资料【技术资料\自定义标签+struts2标签控制访问链接权限】中的《自定义标签(帮助).doc》
7:粗颗粒度权限控制(使用过滤器完成)
分析:
精确到Session的权限控制(判断Session是否存在)
使用过滤器完成粗颗粒的权限控制,如果Session不存在就跳转到首页,如果存在可以通过URL链接访问到对应的操作。
第一步:定义一个过滤器:

public class SystemFilter implements Filter {    /**web容器启动的时候,执行的方法*/    //存放没有Session之前,需要放行的连接    List<String> list = new ArrayList<String>();    public void init(FilterConfig config) throws ServletException {        list.add("/index.jsp");        list.add("/image.jsp");        list.add("/system/elecMenuAction_menuHome.do");    }    /**每次访问URL连接的时候,先执行过滤器的doFilter的方法*/    public void doFilter(ServletRequest req, ServletResponse res,            FilterChain chain) throws IOException, ServletException {        HttpServletRequest request = (HttpServletRequest) req;        HttpServletResponse response = (HttpServletResponse) res;        //获取访问的连接地址        String path = request.getServletPath();        //在访问首页index.jsp页面之前,先从Cookie中获取name,password的值,并显示在页面上(完成记住我)        this.forwordIndexPage(path,request);        //如果访问的路径path包含在放行的List的存放的连接的时候,此时需要放行        if(list.contains(path)){            chain.doFilter(request, response);            return;        }        //获取用户登录的Session        ElecUser elecUser = (ElecUser)request.getSession().getAttribute("globle_user");        //放行        if(elecUser!=null){            chain.doFilter(request, response);            return;        }        //重定向到登录页面        response.sendRedirect(request.getContextPath()+"/index.jsp");    }    /**销毁*/    public void destroy() {    }    /**在访问首页index.jsp页面之前,先从Cookie中获取name,password的值,并显示在页面上(完成记住我)*/    private void forwordIndexPage(String path, HttpServletRequest request) {        if(path!=null && path.equals("/index.jsp")){            String name = "";            String password = "";            String checked = "";            Cookie [] cookies = request.getCookies();            if(cookies!=null && cookies.length>0){                for(Cookie cookie:cookies){                    if(cookie.getName().equals("name")){                        name = cookie.getValue();                        /**                         * 如果name出现中文,对中文进行解码                         */                        try {                            name = URLDecoder.decode(name, "UTF-8");                        } catch (UnsupportedEncodingException e) {                            e.printStackTrace();                        }                        checked = "checked";                    }                    if(cookie.getName().equals("password")){                        password = cookie.getValue();                    }                }            }            request.setAttribute("name", name);            request.setAttribute("password", password);            request.setAttribute("checked", checked);        }    }}

第二步:在web容器中添加对应的过滤器:

<!-- 自定义过滤器,要求添加到struts2过滤器的前面 -->    <filter>        <filter-name>SystemFilter</filter-name>        <filter-class>cn.itcast.elec.util.SystemFilter</filter-class>    </filter>    <filter-mapping>        <filter-name>SystemFilter</filter-name>        <url-pattern>*.do</url-pattern>        <url-pattern>*.jsp</url-pattern>    </filter-mapping>

问题:不够友好,访问链接,最好提示【非法操作,系统将会5秒后跳转到登录页面】

修改过滤器类中的操作:
(1)在过滤器中的init方法中添加2个放行的连接:

list.add("/error.jsp");list.add("/system/elecMenuAction_logout.do");

(2)在doFilter的方法重定向到error.jsp
将:

//重定向到登录页面response.sendRedirect(request.getContextPath()+"/index.jsp");修改成://重定向到error.jsp(5秒跳转到登录名页面)response.sendRedirect(request.getContextPath()+"/error.jsp");

(3)error.jsp的内容:

<script>var i=6;var t;function showTimer(){ if(i==0){//如果秒数为0的话,清除t,防止一直调用函数,对于反应慢的机器可能实现不了跳转到的效果,所以要清除掉 setInterval()  parent.location.href="${pageContext.request.contextPath }/system/elecMenuAction_logout.do";  window.clearInterval(t);  }else{  i = i - 1 ;  // 秒数减少并插入 timer 层中  document.getElementById("timer").innerHTML= i+"秒";  }}// 每隔一秒钟调用一次函数 showTimer()t = window.setInterval(showTimer,1000);</script>

注意:Session不应该在服务器一直不清空,如果Session过多,会导致Session压力大,系统变慢,于是要求10分钟如果不操作系统,将Session自动清空。在web.xml中配置

<session-config>    <session-timeout>10</session-timeout></session-config>  

粗颗粒的权限控制的面试:

使用过滤器
在过滤器中定义放行的连接,因为不是每个操作都会存在Session
在过滤器中获取登录后存放的Session,如果Session不为空,则放行,即可以操作定义的业务功能,如果Session为空,则跳转到登录页面。
控制访问的系统必须要存在Session

8:系统中的异常处理+日志备份(使用struts2的拦截器)

操作:

添加异常处理,详情请见【技术资料\自定义拦截器(实现异常处理+细颗粒权限控制)\异常处理】中的《struts2的拦截器实现异常处理.doc》
项目中的异常处理面试:
使用struts2的拦截器
在拦截器中的doIntercept()方法定义try… catch异常
如果Action、Service、Dao没有抛出异常,则在try模块中指定正确操作的页面,例如:
result = actioninvocation.invoke();
return result;
result跳转到正确的页面
如果Action、Service、Dao抛出异常,则在catch模块中,获取异常,使用log4j存放到指定的日志文件中,通过return “errorMsg”;跳转到错误页面。

9:细颗粒权限控制(使用struts2的拦截器)

操作:
详情参考【技术资料\自定义拦截器(实现异常处理+细颗粒权限控制)\细颗粒度权限控制】中的《struts2的拦截器实现细颗粒度权限控制.doc》

要求:登录操作ElecMenuAction类中的方法,要求不需要添加到struts2的自定义拦截器中
此时可以在struts.xml中定义:
这里写图片描述

问题:为什么在struts2的拦截器中都要使用roleID,mid,pid去查询一遍数据库,而为什么不从Session中获取mid的值和注解上定义的mid的值进行比较呢?
回答:此时不安全,如果盗用账户,登录系统(一定有Session),即可以操作每一个执行的方法。但是由于挂失后,数据库存放的数据发生变化,操作每一个功能之前都会先查询权限,这样查询才能保证数据安全。

细颗粒的权限控制的面试:
使用struts2的拦截器
定义一个注解(mid和pid),对应权限code和父级权限的code,将注解添加到Action类中方法的上面
每个Action类的方法上添加注解(mid=””,pid=””),表示方法的惟一标识(即该方法所具有的权限)
在struts2的拦截器中,从Session中获取角色ID,获取Action类方法上的注解(mid和pid),使用角色ID,mid和pid查询角色权限表,判断当前用户是否可以操作该方法。

10:今天知识点总结

系统上线前的准备:
这里写图片描述

权限表的数据:
这里写图片描述

11:需要掌握的知识点总结

重点:登录操作,项目中后台校验和前台校验,hibernate的懒加载处理
了解:验证码,记住我
必须掌握的分析问题的能力思想:
例如:粗颗粒权限控制
异常处理+日志备份
细颗粒权限控制

0 0
原创粉丝点击