十七、WEB项目开发之“ajax”请求之Session超时

(一)案例背景

public class SessionInterceptor implements HandlerInterceptor {
    private static final Logger LOGGER = LoggerFactory.getLogger(SessionInterceptor.class);
    /**
     * 在进入Handler方法(就是Controller中映射路径对应的方法)执行之前执行本方法
     * @return  true:执行下一个拦截器,直到所有的拦截器都执行完毕,再执行拦截的Controller中的Handler方法
     *         false:从当前的拦截器往回执行所有拦截器的afterCompletion()方法,再退出拦截器链,不再执行Handler方法
     */
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
        HttpSession session = httpServletRequest.getSession();
        //若Session中有User,说明用户已经登录,可以继续执行后续代码
        if (session.getAttribute(SessionKeyConst.SESSION_USER) != null){
            pushMenuInfoInSession(session);
            return true;
        }
        //验证Cookie中是否有登陆标识
        if (validateLoginWithCookie(httpServletRequest)){
            pushMenuInfoInSession(session);
            return true;
        }
        //若Session中没有User,说明用户未登录或登录超时,跳转到登录界面
        httpServletRequest.getRequestDispatcher("/login/sessionTimeout").forward(httpServletRequest, httpServletResponse);
        return false;
    }
    }

  上述代码的主要功能就是:访问服务器内某个页面,首先检查Session中是否有已经登录用户的信息;若没有,再验证Cookie中是否有登录标识;若还是没有,则跳转到登录页面。

  这样的逻辑对于普通的form表单请求都没有什么问题,但是对于“Ajax”请求,则会出问题。

(二)问题复现
  对于“Ajax”请求,比如当Session超时时,就无法跳转到登录页面,服务器会将登录页面的所有“Html文本”返回给请求作为响应,如下图:
这里写图片描述

  这里我来复现下Session超时:当服务器正常访问时,点击“超级管理员组”,会发送“Ajax”请求,返回响应的Json数据,如下图:
这里写图片描述

  此时我重启服务器,则上图会话过程中创建的Session就失效了,我们认为制造了一次Session超时,此时我们再点击“超级管理员组”,会发送“Ajax”请求,由于Session已经失效,经过本文一开始的Session拦截器,就会跳转到登录页面,但由于是“Ajax”请求,无法跳转到登录页面,服务器会将登录页面的所有“Html文本”返回给请求作为响应:
这里写图片描述

  对于这样的返回结果显然对于使用者是不友好的!

(三)解决办法
  需要前后端配合解决!

  1.后端
  (1)Session拦截器
  判断是否是Ajax请求(主要看请求头中是否有”X-Requested-With”),若是,则将跳转路径放到Header中。这里我们做了一个统一的处理,所有的Session超时,统一由请求”/login/sessionTimeout”。

        /*
         *     判断是否是Ajax请求(主要看请求头中是否有"X-Requested-With"),若是,则将跳转路径放到Header中。
         * 这里我做了一个统一的处理,所有的Session超时,统一由请求"/login/sessionTimeout"
         */
        if (httpServletRequest.getHeader("X-Requested-With") != null){
            String basePath = httpServletRequest.getScheme() + "://" + httpServletRequest.getServerName()
                    + ":" +httpServletRequest.getServerPort() + httpServletRequest.getServletContext();
            httpServletResponse.addHeader("SessionTimeoutPath", basePath + "/login/sessionTimeout");
        }

  (2)Session超时的请求处理

    @RequestMapping("/login/sessionTimeout")
    public String sessionTimeout(Model model){
        model.addAttribute(PageCodeEnum.KEY, PageCodeEnum.LOGIN_TIMEOUT);
        return "/system/sessionOut";
    }

  2.前端
  (1)封装Ajax请求

//对jquery的ajax方法进行二次封装,增加超时时间和对Session超时的统一处理
common.ajax1 = function (param) {
    /*
     * 利用Jquery的extend()方法,把传进来的参数,与我这里自定义的参数进行合并,意思是:
     *     若外面(比如param就是用户传进来的参数)已规定超时时间,则覆盖里面自定义的“timeout”;
     *     若未穿超时时间,则以自定义的超时时间为准
     */
    var mergeParam = $.extend({
        timeout:10000
    }, param, {
        /*
         * “complete”:只要ajax请求完毕就会调用,不管成功与否
         *  问题:前面我们说了,若用户传过来的参数在我们自定义的变量之后(比如param在“timeout”之后),
         *        若参数包含了我们这里扩展的变量,则会覆盖我们定义的变量。而我们自定义的“complete”又是在
         *        param之后,所以所以若用户调用ajax的时候,也用了complete方法怎么办,那么则会被我们自定义的
         *        complete方法覆盖。遇到这种问题怎么解决?比如:
         *  common.ajax1({
         *     ....
         *     complete ; function(){
         *         ....
         *     }
         *  });
         *  解决办法:
         *      首先判断Session是否超时,若超时,不论用户调不调用complete方法,都会跳转至登录页面
         *      其次,若Session未超时,且用户传参中包括complete,并且complete是方法不是字段,
         *      则执行用户调用的complete方法
         */
        complete : function (response) {
            //Session超时
            if (response.getResponseHeader("SessionTimeoutPath")){
                //跳转到超时请求
                location.href = response.getResponseHeader("SessionTimeoutPath");
            } else {
                //若Session未超时,且用户传参中包括complete,且complete是方法不是字段
                if (param.complete && typeof param.complete === "function"){
                    //执行用户调用的complete方法
                    param.complete();
                }
            }
        }
    });
    $.ajax(mergeParam);
}

  到这里,基本处理已经完成,但是又出现问题了,再次模拟Session超时,登录界面始终在框架内显示,无法进入到我们正常的登录界面,如下图:
这里写图片描述
  我们正常的登录界面是:
这里写图片描述

(2)在sessionTimeout.jsp中进行登录页面的显示处理

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html lang="zh-cn">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>session超时</title>
    <script src="${basePath}/resources/js/common/common.js" type="text/javascript"></script>
    <script type="text/javascript">
        common.showMessage('${pageCode.msg}');
        //当前窗口
        var topWindow = window;
        //若当前窗口的父窗口不是自己,说明当前窗口不是最外围窗口
        while (topWindow.parent !== topWindow){
            topWindow = topWindow.parent;
        }
        //跳转到登录页面
        topWindow.location.href = "${basePath}/login";
    </script>
</head>
<body>

</body>
</html>
Ajax请求session超时处理流程 java服务器端处理: SessionValidateFilter中修改: if (ServerInfo.isAjax(request)) { request.setAttribute("statusCode", 301); request.setAttribute("message", "Session timeout!"); response.sendRedirect(response.encodeRedirectURL("/ajaxDone.jsp"); else { response.sendRedirect(response.encodeRedirectURL(this.loginUrl + java.net.URLEncoder.encode(backToUrl, "UTF-8"))); } ajaxDone.jsp页面 <%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"%> { statusCode:${statusCode}, message:"${message}", objectId:"${objectId}" } js客户端处理: ajax load页面碎片处理: 自己写一个loadUrl()方法,不能使用jquery自带的load(). 当客户端调用loadUrl()超时,弹出一个登录框,并加一个背景层下面的整个浏览器.这时浏览器窗口内容不能变,只是上面加了一个登录框和一个背景层 当用户输入username and password登录成功后,去掉登录框和背景层.这时用户可以继续操作. 登录失败alert出错信息,浏览器窗口内容还是不变. var DWZ = { loginUrl:"/render.do?method=login", ajaxDoneEval:function (json) { //session timeout try{ return eval('(' + json + ')'); } catch (e){ return {}; } } }; (function($){ $.extend({ loadUrl: function(url,data,callback){ var aData = data || {}; aData["timestamp"] = new Date().getTime(); var $this = $(this); $.get(url, aData, function(data){ var json = DWZ.ajaxDoneEval(data); if (json.statusCode==301){ alertMsg.error(json.message, {okCall:function(){ window.location = "/render.do?method=login"; //popLoginWin(); }}); } else { $this.html(data).initUI(); if (jQuery.isFunction(callback)) callback(); } }); } }); })(jQuery); ajax post 表单数据处理: 当客户端ajax提交表单超时, 弹出一个登录框,并加一个背景层下面的整个浏览器. 当用户输入username and password登录成功后,去掉登录框和背景层.这时用户可以继续操作. 登录失败alert出错信息. $.post(form.action, params, callback(json){ if (json.statusCode == 301) { //301 状态表示 session timeout popLoginWin(); }else if (json.statusCode == 300) { //300 状态表示 操作失败 alertMsg.error(json.message); } else if(json.statusCode == 200) { //300 状态表示 操作成功 alertMsg.correct(json.message); } }, "json");
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值