处理重复提交表单时遇到的问题

前言

这里只是给自己做个笔记,方便自己以后改进。对别人的参考价值可能不大。本文并没有完全解决问题,因此说是给自己做个笔记。把问题记下,以后再慢慢解决。

几个问题

1.登录模块中,连续发起请求,为什么后面的请求会覆盖前面的请求(在其他模块却不会)
2.使用脚本方法提交表单时,一次提交发起了两次请求。使用form的url的方式提交表单,一次提交直发起一次请求
3.登录模块中,要怎么处理重复请求问题
4.ajax返回的不是Object对象的问题
5.重复请求时,ajax的显示变得很奇怪,页面空白了,数据直接加载到空白页面上了

为什么要处理重复请求问题

如果有网络延迟的话,用户如果不耐烦,一直惦记提交按钮,那么会导发起多次请求,这对服务器不友好。因此要处理重复提交请求的问题。

怎么处理

前端可以进行处理,后端也可以进行处理。后端的尝试了,但是失败了(原因是第一个请求被后面的请求压住,无法反馈信息给前端。而后面的请求被拦截器拦下。因此页面变成一片空白)。
因此,尝试在前端进行处理。

我的情况

我要解决的是登录模块。有些问题,在登录模块中会出现,在其他模块中又不会,好奇怪。

前后端对应的代码:

@RequestMapping(value="/login",method = RequestMethod.POST)//只接收post请求,因此如果是通过url访问的话,则不予处理
public ModelAndView login(@RequestParam("loginname") String loginname,
                          @RequestParam("password") String password,
                          HttpSession session,
                          ModelAndView mv){

    //模拟网络延迟
    try{Thread.sleep(5000);}catch (Exception e){}

    User user = hrmService.login(loginname, password);
    if(user!=null){
        session.setAttribute("user_session",user);
        mv.setViewName("redirect:main");
    }else{
        mv.addObject("message","您输入的账号或密码不正确,请重新输入");
        mv.setViewName("forward:toLogin");//不要写成了mv.setViewName("loginForm");否则,如果前端是通过脚本方法提交表单,前端将不会有任何变化,而写成mv.setViewName("forward:loginForm");则是错误的写法,会报404
    }
    return mv;
}
<div class="easyui-window" title="Login" style="width:320px;height:220px;" collapsible="false" minimizable="false" maximizable="false" closable="false" draggable="false" resizable="false">
    <form url="/user/login" method="post" style="padding:10px 20px 10px 40px;">
        <p>${message} </p>
        <p>账号:<input class="easyui-textbox" name="loginname" data-options="prompt:'请输入账号'"></p>
        <p>密码:<input class="easyui-textbox" name="password" data-options="prompt:'请输入密码'" type="password"></p>
        <div style="padding:5px;text-align:center;">
            <button class="easyui-linkbutton" type="submit" >登录</button>
        </div>
    </form>
</div>

重复请求时后面的请求会压住前面的请求

在模拟重复请求时发现的问题:
我在登录验证方法中加入Thread.sleep(2000)来模拟网络延迟。发现一个很奇怪的问题,如果没有处理重复请求的问题,如果连续点击提交按钮,后面的请求会压住前面的请求,从前端控制台上观察到是前面的请求变成了cancelled状态,最后返回的页面是最后一个请求对应的结果。在后端控制台上我观察到的是,每个请求都能进到controller方法中,但是前面的请求在返回ModelAndView对象后,浏览器却没有给予响应。

为什么后面的请求会压住前面的请求?(这个问题网上也有人遇到过,但是我没看到有解决的方案)

但是!我在其他模块的一个add方法中也模拟了网络延迟,但是却不会出现这个问题。好奇怪。

登录界面如何处理表单重复提交的问题

这里针对的是在前端处理这个问题。

要处理重复提交的问题,就要在第一次点击提交按钮之后,要禁用按钮的点击功能,然后等到第一个请求返回结果(无论成功与失败),再恢复按钮的点击功能。

而我这里呢,使用的是easyui前端框架。
采用在form表单上填写url属性,那么我就无法处理重复提交请求的问题了。因为我禁用按钮后无法恢复按钮点击功能。
因此,得使用异步请求,即通过在方法中使用ajax来提交请求。这个方法大致如:

function subbmitForm(){
    $("#btn").attr("disabled",true);//禁用按钮点击
    //异步请求
    $('#ff').form('submit', {
        url:...,
        onSubmit: function(){
            // do some check
            // return false to prevent submit;
        },
        success:function(data){
            alert(data)
            $("#btn").attr("disabled",false);//恢复按钮
        }
    });
}

这样写的话,重复提交的问题就解决了。但是呢,有一个更严重的问题了。由于ajax请求只是返回一个结果,但是它不会加载成页面。因此,使用ajax发起请求,无论登录成功与否,都是返回了一个html页面代码,但是却没有加载出来。这该怎么办。

理一下自己的思路:我现在的困扰是,如果使用form的url的形式来提交表单,那么就无法进行重复表单的处理。(我理解饿的处理就是在点击提交按钮之后禁用按钮功能,然后返回结果后,恢复功能)(网上有一种处理方式是,一点击就禁用,也就是说只能点击一次,那样的话,如果密码填错了,就没办法再次登录了)(网上还有一种做法是点击提交按钮之后,禁用按钮点击功能指定时间,1秒或者2秒。这个倒是可行的。)如果使用ajax的形式来提交表单,那么我不知道该怎么实现页面的跳转。

思清了思路,就知道怎么百度提问了:ajax如何实现页面跳转。参考文章:
http://blog.csdn.net/xyw591238/article/details/51441772

这篇文章说了,ajax只接收最后返回的值,不会响应跳转请求更改浏览器地址栏地址的转向,你需要用js判断ajax的返回值是否要跳转,然后设定location.href实现跳转。

尝试解决重复提交表单的问题

那我知道该怎么做了:

修改controller方法,原本的login方法返回的是ModelAndView对象,但是现在改成返回一个字符串。
如果登录验证失败,则返回null,然后在前端通过jq代码让页面显示那句“账号密码填写有误”的提示。如果登录验证正确,则返回字符串“/user/main”,这是主界面的路径。然后在前端通过代码实现页面跳转。

这样不就可以了吗!!

后端代码改成:

@ResponseBody//新增,既然方法返回的是String,就得加上该注解,才能直接响应前台
@RequestMapping(value="/login",method = RequestMethod.POST)//只接收post请求,因此如果是通过url访问的话,则不予处理
public String login(@RequestParam("loginname") String loginname,
                          @RequestParam("password") String password,
                          HttpSession session){

    //模拟网络延迟
    try{Thread.sleep(5000);}catch (Exception e){}

    User user = hrmService.login(loginname, password);
    if(user!=null){
        session.setAttribute("user_session",user);
        return "/user/main";
    }else{
        return null;
    }
}

前端代码改成:

<div class="easyui-window" title="Login" style="width:320px;height:220px;" collapsible="false" minimizable="false" maximizable="false" closable="false" draggable="false" resizable="false">
    <form id="form" method="post" style="padding:10px 20px 10px 40px;">
        <p id="p">欢迎登录</p>
        <p>账号:<input class="easyui-textbox" name="loginname" data-options="prompt:'请输入账号'"></p>
        <p>密码:<input class="easyui-textbox" name="password" data-options="prompt:'请输入密码'" type="password"></p>
        <input name="token" value="${sessionScope.token}" >
        <div style="padding:5px;text-align:center;">
            <button id="btn" class="easyui-linkbutton" onclick="submitForm()">登录</button>
        </div>
    </form>
</div>

</body>

<script>
    function submitForm() {
        $("#btn").attr("disabled",true);//禁用按钮
        $('#form').form('submit',{
            url:'/user/login',
            success:function (data) {
                if(data){
                    window.location.href=data;//页面跳转
                }else{
                    $('#p').text("您输入的账号或密码不正确,请重新输入");//给出提示
                }
                $("#btn").attr("disabled",false);//恢复按钮
            }
        });
    }
</script>

测试发现,成功了。登录成功与失败,显示正常了。

ajax返回的结果不是Object对象的问题

我想改进上面代码,让代码写的规范点:
加个DTO(Result类,里面有布尔型的success属性,和泛型的info属性),然后让控制层方法返回一个封装好的结果。前端ajax中再判断data.success是否为true来决定执行不同的操作。

可是失败了。controller方法返回一个封装好的Result对象。但是在前台通过console.log(data)查看返回的对象,不是一个对象而是字符串。这很奇怪。控制台还有一个信息:

Resource interpreted as Document but transferred with MIME type application/json: "http://localhost:8080/user/login".

想不懂。

上面的问题的引申的另一个问题

另外,上面成功的那个例子,如果前端没有处理重复提交的问题,最后一次请求的数据居然直接加载显示到一个空白页面上。而没有按照success事件里的代码执行。不知道这是什么为什么。

我现在的解决方法

就是标题“尝试解决重复提交表单的问题”的那一部分的代码。前端通过脚本代码处理重复请求问题,这样通过脚本方法提交表单,一次提交只会发出一次请求,连续点击,也不会发出多次请求。也不去扩展DTO,就不会出现上面说的几个问题了。

除非用户的浏览器禁止了javascrpit。

在其他的模块,不会出现重复请求时后面请求压住前面请求的问题。这样前端处理和后端处理都可以完成。登录模块之所以用后端处理解决不了就是因为后面的请求压住了前面的请求。

知识点

PS:DTO类如果少了getter方法,会出现406错误。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值