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

本文介绍了如何使用Struts2的validator进行后台校验,处理Hibernate的懒加载问题,以及如何利用jQuery的ztree插件动态加载树型结构。还涉及到系统登录的验证码、记住我功能,并探讨了细颗粒权限控制的实现,包括自定义拦截器和Struts2的注解使用。
摘要由CSDN通过智能技术生成

项目第六天(系统登录)

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的懒加载处理
了解:验证码,记住我
必须掌握的分析问题的能力思想:
例如:粗颗粒权限控制
异常处理+日志备份
细颗粒权限控制

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值