java_WEB开发 防刷新

客户端处理: 
面对客户端我们可以使用Javascript脚本来解决,如下

1。重复刷新、重复提交 
Ways One:设置一个变量,只允许提交一次。 
<script language="javascript"> 
var checkSubmitFlg = false; 
function checkSubmit() { 
if (checkSubmitFlg == true) { 
return false; 
} 
checkSubmitFlg = true; 
return true; 
} 
document.ondblclick = function docondblclick() { 
window.event.returnValue = false; 
} 
document.onclick = function doconclick() { 
if (checkSubmitFlg) { 
window.event.returnValue = false; 
} 
} 
</script> 
<html:form action="myAction.do" method="post" onsubmit="return checkSubmit();">

Way Two : 将提交按钮或者image置为disable 
<html:form action="myAction.do" method="post" 
onsubmit="getElById('submitInput').disabled = true; return true;"> 
<html:image style src="images/ok_b.gif"  /> 
</html:form> 

2。防止用户后退 
这里的方法是千姿百态,有的是更改浏览器的历史纪录的,比如使用window.history.forward()方法;有的是“用新页面的URL替换当前的历史纪录,这样浏览历史记录中就只有一个页面,后退按钮永远不会变为可用。”比如使用javascript:location.replace(this.href); event.returnValue=false;


2.服务器端的处理(这里只说Struts框架的处理) 
利用同步令牌(Token)机制来解决Web应用中重复提交的问题,Struts也给出了一个参考实现。

基本原理: 
服务器端在处理到达的请求之前,会将请求中包含的令牌值与保存在当前用户会话中的令牌值进行比较, 
看是否匹配。在处理完该请求后,且在答复发送给客户端之前,将会产生一个新的令牌,该令牌除传给 
客户端以外,也会将用户会话中保存的旧的令牌进行替换。这样如果用户回退到刚才的提交页面并再次 
提交的话,客户端传过来的令牌就和服务器端的令牌不一致,从而有效地防止了重复提交的发生。

if (isTokenValid(request, true)) { 
// your code here 
return mapping.findForward("success"); 
} else { 
saveToken(request); 
return mapping.findForward("submitagain"); 
}

Struts根据用户会话ID和当前系统时间来生成一个唯一(对于每个会话)令牌的,具体实现可以参考 
TokenProcessor类中的generateToken()方法。

1. //验证事务控制令牌,<html:form >会自动根据session中标识生成一个隐含input代表令牌,防止两次提交 
2. 在action中:


//<input type="hidden" name="org.apache.struts.taglib.html.TOKEN" 
// value="6aa35341f25184fd996c4c918255c3ae"> 
if (!isTokenValid(request)) 
errors.add(ActionErrors.GLOBAL_ERROR, 
new ActionError("error.transaction.token")); 
resetToken(request); //删除session中的令牌

3. action有这样的一个方法生成令牌 
protected String generateToken(HttpServletRequest request) { 
HttpSession session = request.getSession(); 
try { 
byte id[] = session.getId().getBytes(); 
byte now[] = 
new Long(System.currentTimeMillis()).toString().getBytes(); 
MessageDigest md = MessageDigest.getInstance("MD5"); 
md.update(id); 
md.update(now); 
return (toHex(md.digest())); 
} catch (IllegalStateException e) { 
return (null); 
} catch (NoSuchAlgorithmException e) { 
return (null); 
} 
} 

总结 
对于重复提交、重复刷新、防止后退等等都是属于系统为避免重复记录而需要解决的问题,在客户端去处理需要针对每一种的可能提出相应的解决方案,然而在服务器端看来只不过是对于数据真实性的检验问题,基于令牌的处理就是一劳永逸的方法。

同时我们也看到,从不同的角度去看待问题,其解决的方法也是不同的。客户端更追求的是用户的操作,而服务端则将注意力放在了数据的处理上,所以在某个对于服务器端看似容易的问题上,用客户端来解决却麻烦了很多!反之依然。所以在某些问题的处理上我们需要综合考虑和平衡,是用客户端来解决?还是用服务器端来处理?
         在java web工程中,当jsp向Servlet提交请求时,如何防止刷新提交(F5)? 

         第一种解决方法,参照老紫竹的思路: 


         可以使用的方法有,在jsp中定义一个变量值,这个变量值应该是唯一的,可以使用算法来保证生成数据的唯一性,例如hash算法,或者生成一个随机数(Random),并将这个数值保存到一个Set中,并将Set保存在session中,提交Servlet时将这个参数传递过去,在Servlet中的处理:接到参数和session中的set后,判断set中有没有传来的参数值,如果没有则页面是刷新造成的,并不是来自页面的提交,这时不做insert data的处理。 

         jsp 代码 : 

         <% 
        //生成一个formhash,算法可以自己定,不随便重复就可以了
        Random ran = new Random();
        String formhash = String.valueOf(ran.nextInt());
        //读取当前session里面的hashCode集合,此处使用了Set,方便判断。
        Set<String> formhashSession = (Set<String>) session.getAttribute("formhashSession");
        if (formhashSession == null) {
             formhashSession = new HashSet<String>();
        }
        // 检测重复问题
        while (formhashSession.contains(formhash)) {
             formhash = String.valueOf(ran.nextInt());
        }
        // 保存到session里面
        formhashSession.add(formhash);
        // 保存
        session.setAttribute("formhashSession", formhashSession);
     %> 

    <form>中增加:<input type="hidden" name="formhash" >    

         Servlet代码: 

         // 拿到表单的formhash
         String formhash = request.getParameter("formhash");
         // 拿到session里面的集合
         Set<String> formhashSession = (Set<String>) session.getAttribute("formhashSession");
          // 如果没有,则是重复提交,或者非法提交
          if (formhashSession == null || !formhashSession.contains(formhash)) {
                System.out.println("重复提交!");
           }else{ 

                 //正常的操作 

           } 

           // 最后,如果操作成功,从session里面把这个formhash 删掉!
           formhashSession.remove(formhash);
           session.setAttribute("formhashSession", formhashSession); 

参考文章:http://blog.csdn.net/java2000_net/archive/2008/02/25/2119298.aspx 

          第二种解决方法, 参考struts的token(令牌)机制: 

          在提交的时候在Servlet中根据用户的sessionid和当前时间的long值生成一个令牌(每次提交都会生成一个新令牌),将令牌保存在该用户的会话中,并将令牌的值以request属性形式传到前端页面,在前端页面的form中增加传递令牌的隐藏域<input type="hidden" name="clientToken" value="<%=clientToken%>" />,提交form的时候,也会将clientToken传入Servlet,如果session中保存的令牌值与传入的不同,则是重复提交,因为每次请求Servlet都会生成新的令牌,刷新时的令牌值是旧的令牌值,不是最新的令牌值。 

           jsp代码: 

           <% 
    String clientToken = (String)request.getAttribute("clientToken");
    clientToken = clientToken==null?"":clientToken;
           %> 

<form>中增加<input type="hidden" name="clientToken" value="<%=clientToken%>" /> 

          Servlet代码: 

          String clientToken = request.getParameter("clientToken");
          String sessionToken = (String) session.getAttribute("token"); 

          if(sessionToken!=null&&!clientToken.equals(sessionToken)){
                 System.out.println("请不要重复提交!");
          }else{ 

                  //正常的操作 

          } 

           //生成新令牌
           String token = generateToken(request);
           request.setAttribute("clientToken", token);
           //替换旧令牌
           session.setAttribute("token", token);