(一)
两种防止重复调提交的方法
1 jsp页面提交
2 struts的action类型防止提交
Jsp的页面提交类型:
1.将jsp页面中设计状态位,这样的话,改变状态位就不能进行支持操作。
<script language="javascript">
var checkSubmitFlg = false;
function checkSubmit() {
if (!checkSubmitFlg) {
checkSubmitFlg = true;
return true;
}else{
alert("不能重复提交");
return false;
}
}
</script>
<form action="XXXX" method="POST" οnsubmit="return checkSubmit()">
内容:<input type="text" name="content" value=""/><br>
<input type="submit" value="提交"><br>
</form>
此类做法类似的还有:提交时将submit标签设置为不可操作的属性,或者当点了提交按钮后 使按钮不可用 或者可以在页面加个验证码 如果验证不通过就不跳转么!
下面是一种js解决方法:
通过js控制,提交后将提交按钮所在的table块 通过
document.forms[0].submitType.value = v;
var v = document.getElementById(t);
v.innerHTML = "数据提交中...";
这种方式替换掉就ok了
第二类, Struts的action类型的防止提交:
1,struts2的防止重复提交,也使用到了token(令牌机制),并且使用到了struts2的一个叫token的拦截器
添加商品页面:
1. <body>
2. <s:form action="product_doAddProduct.action" method="post">
3. <s:token></s:token>
4. <s:textfield name="name" label="商品名称"></s:textfield>
5. <s:textfield name="price" label="商品价格"></s:textfield>
6. <s:submit value="提交"></s:submit>
7. </s:form>
8. </body>
<body>
<s:form action="product_doAddProduct.action" method="post">
<s:token></s:token>
<s:textfield name="name" label="商品名称"></s:textfield>
<s:textfield name="price" label="商品价格"></s:textfield>
<s:submit value="提交"></s:submit>
</s:form>
</body>
struts.xml 配置文件
1. <package name="token" extends="struts-default" namespace="/jsp">
2. <action name="product_*" class="org.cric.action.ProductAction" method="{1}">
3. <result name="success">/jsp/product.jsp</result>
4. <result name="invalid.token">/jsp/error.jsp?message=重复提交</result>
5.
1. <interceptor-ref name="token"/>
1. <SPAN style="WHITE-SPACE: pre"> </SPAN><interceptor-ref name="defaultStack"/>
6. </action>
7. </package>
<package name="token" extends="struts-default" namespace="/jsp">
<action name="product_*" class="org.cric.action.ProductAction" method="{1}">
<result name="success">/jsp/product.jsp</result>
<result name="invalid.token">/jsp/error.jsp?message=重复提交</result>
<interceptor-ref name="token"/>
<interceptor-ref name="defaultStack"/>
</action>
</package>
动作层:
1. public String doAddProduct() throws Exception{
2. System.out.println("添加商品了……");
3. Thread.sleep(10000);
4. return "success";
5. }
public String doAddProduct() throws Exception{
System.out.println("添加商品了……");
Thread.sleep(10000);
return "success";
}
错误页面:
1. <body>
2. ${param['message']}
3. </body>
<body>
${param['message']}
</body>
显示商品信息页面:
1. <body>
2. 商品名称:<s:property value="%{name}"/><br>
3. 商品价格:<s:property value="%{price}"/>
4. </body>
<body>
商品名称:<s:property value="%{name}"/><br>
商品价格:<s:property value="%{price}"/>
</body>
注意:
a:当拦截器拦截到 当发生重复提交的action时候,会跳转到invalid.token指定的页面。
b:为了使用同一的错误页面,在错误页面上接受到message参数的值,可以在error.jsp使用 ${param['message']}
c:在提交页面的form中添加<s:token/>标签,需要在页面中加上。
2,struts2的防止重复提交,也使用到了token(令牌机制),并且使用到了struts2的一个叫token-session的拦截器
Token Session拦截器扩展了Token并提供了一种更复杂的服务。Token Session拦截器不想Token拦截器那样会返回一种特殊的结果并添加一个动作错误,它采用的做法只是阻断后续的提交,而这么做的后果是用户将看到同一的响应,就好像只有一次提交那样!
1. <package name="token" extends="struts-default" namespace="/jsp">
2. <action name="product_*" class="org.cric.action.ProductAction" method="{1}">
3. <result name="success">/jsp/product.jsp</result>
4. <interceptor-ref name="token-session">
5. <param name="includeMethods">doAddProduct</param>
6. </interceptor-ref>
7. <interceptor-ref name="defaultStack"/>
8. </action>
9. </package> <package name="token" extends="struts-default" namespace="/jsp">
<action name="product_*" class="org.cric.action.ProductAction" method="{1}">
<result name="success">/jsp/product.jsp</result>
<interceptor-ref name="token-session">
<param name="includeMethods">doAddProduct</param>
</interceptor-ref>
<interceptor-ref name="defaultStack"/>
</action>
</package>
注意:
a:includeMethods 指定需要拦截的方法 excludeMethods 指定不需要拦截的方法,多个方法使用逗号分隔
b:把第一个程序的配置文件换成这个,不需要错误页面。
<s:token/>的原理就是在session总保存一个字符串。
仔细观察包含<s:token/>的页面会发现这些东西
<input type="hidden" name="struts.token.name" value="struts.token"/>
<input type="hidden" name="struts.token" value="BXPNNDG6BB11ZXHPI4E106CZ5K7VNMHR"/>
当页面提交的时候时候他会判断这个值是不是和session的一样,如果是一样的哪么OK,没问题你可以提交,并且会修改session里的值。哪么如果你刷新,那你页面上的值和session的值就不一样的,struts就不允许你提交数据了。就是这个原理。
我们是用了最笨的方法.是针对refresh页面时重复提交的问题.当进入一张表单时往session写个值设为true,提交后在action处理的时候往session里设这个值为false,在方法里判段它为true时才执行数据库等操作.就杜绝了重复提交.
forward设置redirect=true是最简单的设置了也是最容易想到的啊
Struts已经内置了能够防止用户重复提交同一个HTML表单的功能。它采用的办法在其他一些用来开发Web应用程序的技术里也可以见到:让服务器生成一个唯一标记,并在服务器和表单里保存一份这个标记的副本。此后,在用户提交表单的时候,表单里的标记将虽说这其他请求参数一起发送到服务器,服务器将对它收到的标记和它留存的标记进行比较。如果两者匹配,这次从提交来的表单就是被认为是有效的,服务器将对之做出必要的处理并重新设置一个标记。随后(因为不小心)提交相同的表单就会失败,因为服务器上的标记已经重置。
<s:token/>的原理就是在session总保存一个字符串。
仔细观察包含<s:token/>的页面会发现这些东西
<input type="hidden" name="struts.token.name" value="struts.token"/>
<input type="hidden" name="struts.token" value="BXPNNDG6BB11ZXHPI4E106CZ5K7VNMHR"/>
当页面提交的时候时候他会判断这个值是不是和session的一样,如果是一样的哪么OK,没问题你可以提交,并且会修改session里的值。哪么如果你刷新,那你页面上的值和session的值就不一样的,struts就不允许你提交数据了。就是这个原理。
我们是用了最笨的方法.是针对refresh页面时重复提交的问题.当进入一张表单时往session写个值设为true,提交后在action处理的时候往session里设这个值为false,在方法里判段它为true时才执行数据库等操作.就杜绝了重复提交.
forward设置redirect=true是最简单的设置了也是最容易想到的啊
Struts已经内置了能够防止用户重复提交同一个HTML表单的功能。它采用的办法在其他一些用来开发Web应用程序的技术里也可以见到:让服务器生成一个唯一标记,并在服务器和表单里保存一份这个标记的副本。此后,在用户提交表单的时候,表单里的标记将虽说这其他请求参数一起发送到服务器,服务器将对它收到的标记和它留存的标记进行比较。如果两者匹配,这次从提交来的表单就是被认为是有效的,服务器将对之做出必要的处理并重新设置一个标记。随后(因为不小心)提交相同的表单就会失败,因为服务器上的标记已经重置。
(二)
Struts的Token(令牌)机制能够很好的解决表单重复提交的问题,基本原理是:服务器端在处理到达的请求之前,会将请求中包含的令牌值与保存在当前用户会话中的令牌值进行比较,看是否匹配。在处理完该请求后,且在答复发送给客户端之前,将会产生一个新的令牌,该令牌除传给客户端以外,也会将用户会话中保存的旧的令牌进行替换。这样如果用户回退到刚才的提交页面并再次提交的话,客户端传过来的令牌就和服务器端的令牌不一致,从而有效地防止了重复提交的发生。
这时其实也就是两点,第一:你需要在请求中有这个令牌值,请求中的令牌值如何保存,其实就和我们平时在页面中保存一些信息是一样的,通过隐藏字段来保存,保存的形式如: 〈input type="hidden" name="org.apache.struts.taglib.html.TOKEN" value="6aa35341f25184fd996c4c918255c3ae"〉,这个value是TokenProcessor类中的generateToken()获得的,是根据当前用户的session id和当前时间的long值来计算的。第二:在客户端提交后,我们要根据判断在请求中包含的值是否和服务器的令牌一致,因为服务器每次提交都会生成新的Token,所以,如果是重复提交,客户端的Token值和服务器端的Token值就会不一致。下面就以在数据库中插入一条数据来说明如何防止重复提交。
在Action中的add方法中,我们需要将Token值明确的要求保存在页面中,只需增加一条语句:saveToken(request);,如下所示:
public ActionForward add(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
//前面的处理省略
saveToken(request);
return mapping.findForward("add");
}在Action的insert方法中,我们根据表单中的Token值与服务器端的Token值比较,如下所示:
public ActionForward insert(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
if (isTokenValid(request, true)) {
// 表单不是重复提交
//这里是保存数据的代码
} else {
//表单重复提交
saveToken(request);
//其它的处理代码
}
}
其实使用起来很简单,举个最简单、最需要使用这个的例子:
一般控制重复提交主要是用在对数据库操作的控制上,比如插入、更新、删除等,由于更新、删除一般都是通过id来操作(例如:updateXXXById, removeXXXById),所以这类操作控制的意义不是很大(不排除个别现象),重复提交的控制也就主要是在插入时的控制了。
先说一下,我们目前所做项目的情况:
目前的项目是用Struts+Spring+Ibatis,页面用jstl,Struts复杂View层,Spring在Service层提供事务控制,Ibatis是用来代替JDBC,所有页面的访问都不是直接访问jsp,而是访问Structs的Action,再由Action来Forward到一个Jsp,所有针对数据库的操作,比如取数据或修改数据,都是在Action里面完成,所有的Action一般都继承BaseDispatchAction,这个是自己建立的类,目的是为所有的Action做一些统一的控制,在Struts层,对于一个功能,我们一般分为两个Action,一个Action里的功能是不需要调用Struts的验证功能的(常见的方法名称有add,edit,remove,view,list),另一个是需要调用Struts的验证功能的(常见的方法名称有insert,update)。
就拿论坛发贴来说吧,论坛发贴首先需要跳转到一个页面,你可以填写帖子的主题和内容,填写完后,单击“提交”,贴子就发表了,所以这里经过两个步骤:
1、转到一个新增的页面,在Action里我们一般称为add,例如:
public ActionForward add(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
//这一句是输出调试信息,表示代码执行到这一段了
log.debug(":: action - subject add");
//your code here
//这里保存Token值
saveToken(request);
//跳转到add页面,在Structs-config.xml里面定义,例如,跳转到subjectAdd.jsp
return mapping.findForward("add");
}
2、在填写标题和内容后,选择 提交 ,会提交到insert方法,在insert方法里判断,是否重复提交了。
public ActionForward insert(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response){
if (isTokenValid(request, true)) {
// 表单不是重复提交
//这里是保存数据的代码
} else {
//表单重复提交
saveToken(request);
//其它的处理代码
}
}
下面更详细一点(注意,下面所有的代码使用全角括号):
1、你想发贴时,点击“我要发贴”链接的代码可以里这样的:
〈html:link action="subject.do?method=add"〉我要发贴〈/html:link〉
subject.do 和 method 这些在struct-config.xml如何定义我就不说了,点击链接后,会执行subject.do的add方法,代码如上面说的,跳转到subjectAdd.jsp页面。页面的代码大概如下:
〈html:form action="subjectForm.do?method=insert"〉
〈html:text property="title" /〉
〈html:textarea property="content" /〉
〈html:submit property="发表" /〉
〈html:reset property="重填" /〉
〈html:form〉
如果你在add方法里加了“saveToken(request);”这一句,那在subjectAdd.jsp生成的页面上,会多一个隐藏字段,类似于这样〈input type="hidden" name="org.apache.struts.taglib.html.TOKEN" value="6aa35341f25184fd996c4c918255c3ae"〉,
2、点击发表后,表单提交到subjectForm.do里的insert方法后,你在insert方法里要将表单的数据插入到数据库中,如果没有进行重复提交的控制,那么每点击一次浏览器的刷新按钮,都会在数据库中插入一条相同的记录,增加下面的代码,你就可以控制用户的重复提交了。
if (isTokenValid(request, true)) {
// 表单不是重复提交
//这里是保存数据的代码
} else {
//表单重复提交
saveToken(request);
//其它的处理代码
}
注意,你必须在add方法里使用了saveToken(request),你才能在insert里判断,否则,你每次保存操作都是重复提交。
记住一点,Struts在你每次访问Action的时候,都会产生一个令牌,保存在你的Session里面,如果你在Action里的函数里面,使用了saveToken(request);,那么这个令牌也会保存在这个Action所Forward到的jsp所生成的静态页面里。
如果你在你Action的方法里使用了isTokenValid,那么Struts会将你从你的request里面去获取这个令牌值,然后和Session里的令牌值做比较,如果两者相等,就不是重复提交,如果不相等,就是重复提交了。
由于我们项目的所有Action都是继承自BaseDispatchAction这个类,所以我们基本上都是在这个类里面做了表单重复提交的控制,默认是控制add方法和insert方法,如果需要控制其它的方法,就自己手动写上面这些代码,否则是不需要手写的,控制的代码如下:
public abstract class BaseDispatchAction extends BaseAction {
protected ActionForward perform(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
String parameter = mapping.getParameter();
String name = request.getParameter(parameter);
if (null == name) { //如果没有指定 method ,则默认为 list
name = "list";
}
if ("add".equals(name)) {
if ("add".equals(name)) {
saveToken(request);
}
} else if ("insert".equals(name)) {
if (!isTokenValid(request, true)) {
resetToken(request);
saveError(request, new ActionMessage("error.repeatSubmit"));
log.error("重复提交!");
return mapping.findForward("error");
}
}
return dispatchMethod2(mapping, form, request, response, name);
}
}