面试时遇到一个题:怎么防止表单重复提交?
当时想了想,这个题不是很难,简单来说就是验证的问题。于是我很容易想到session。
因为session的原理和这个很像。
我的思路:在表单中加入隐藏字段,作为这个表单的唯一标识。同时再session中记录这个表单的提交次数。
下次再提交,就是重复提交的时候,从session中获取提交次数,判断一下就可以了。
下面给出我的例子:
先写一个简单的新增的操作。
IsRepeatSubmitAction
package org.test.submit.action; import java.io.PrintWriter; import java.util.Date; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.struts2.ServletActionContext; import org.base.MyBaseAction; import org.base.pk.UUIDHexGenerator; import org.test.submit.bean.Develpoer; import org.test.submit.dao.SubmitDao; import com.opensymphony.xwork2.ActionContext; public class IsRepeatSubmitAction extends MyBaseAction { private static final long serialVersionUID = 1L; public String execute() throws Exception{ String pk_userId=(String)UUIDHexGenerator.generate();//生成主键 this.getHttpServletRequest().setAttribute("userId", pk_userId);//放入页面表单的隐藏字段中 this.getHttpSession().setAttribute(pk_userId.trim(), 0);//放入session中 return SUCCESS; } public String goSubmit() throws Exception{ // ActionContext ctx = ActionContext.getContext(); HttpServletResponse response = (HttpServletResponse) ctx.get(ServletActionContext.HTTP_RESPONSE); HttpServletRequest request = (HttpServletRequest) ctx.get(ServletActionContext.HTTP_REQUEST); HttpSession session = request.getSession(); response.setCharacterEncoding("UTF-8"); PrintWriter out = response.getWriter(); String pk_userId_from_page="";//从页面的隐藏字段获取主键 if(this.getDevelpoer()==null){ System.out.println("获取表单错误!"); }else{ pk_userId_from_page=(this.getDevelpoer().getUserId()!=null)?(String)this.getDevelpoer().getUserId().trim():""; if(pk_userId_from_page.equals("")){ System.out.println("传参错误!"); }else{ System.out.println("主键userId:"+pk_userId_from_page); if(this.getHttpSession().getAttribute(pk_userId_from_page)==null){ System.out.println("表单已过期!!"); }else{ int pk_userId_count=(Integer)this.getHttpSession().getAttribute(pk_userId_from_page);//获取该主键提交的次数(没提交是0;提交过是1) if(pk_userId_count==0){ this.getHttpSession().setAttribute(pk_userId_from_page, 1);//置成1--已提交 String addres=submitDao.addDeveloper(this.getDevelpoer());//插入数据 if(addres.equals("success")){ System.out.println("成功写库!"+new Date().toString()); }else{ System.out.println("写库失败!请联系管理员!"+new Date().toString()); } }else{ System.out.println("您已提交过表单!"); } } } } return NONE; } private SubmitDao submitDao; public SubmitDao getSubmitDao() { return submitDao; } public void setSubmitDao(SubmitDao submitDao) { this.submitDao = submitDao; } private Develpoer develpoer; public Develpoer getDevelpoer() { return develpoer; } public void setDevelpoer(Develpoer develpoer) { this.develpoer = develpoer; } }
pojo:Develpoer
package org.test.submit.bean; public class Develpoer { private String userId; private String userName; private String realName; public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getRealName() { return realName; } public void setRealName(String realName) { this.realName = realName; } }
SubmitDao
package org.test.submit.dao; import org.test.submit.bean.Develpoer; public interface SubmitDao { public String addDeveloper(Develpoer develpoer); }
SubmitDaoImpl
package org.test.submit.dao.impl; import org.base.MyHibernateDao; import org.test.submit.bean.Develpoer; import org.test.submit.dao.SubmitDao; public class SubmitDaoImpl extends MyHibernateDao implements SubmitDao{ public String addDeveloper(Develpoer develpoer) { // String userId,String userName,String realName String res="fail"; if(develpoer==null){ }else if(develpoer.getUserId()==null||develpoer.getUserId().trim().equals("")){ res="fail"; }else{ String uname=(develpoer.getUserName()==null)?"":develpoer.getUserName().trim(); String rname=(develpoer.getRealName()==null)?"":develpoer.getRealName().trim(); String sql="insert into lsy_user_develop(USER_ID, USER_NAME, REAL_NAME) " + " values('"+develpoer.getUserId().trim()+"','"+uname+"','"+rname+"')"; this.executeSql(sql); res="success"; } return res; } }
struts_submit.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd"> <struts> <package name="submit" extends="base-struts-default" namespace="/submit"> <!-- 防止表单重复提交 --> <action name="addDeveloper" class="org.test.submit.action.IsRepeatSubmitAction"> <result name="success">/jsp/test/repeat.jsp</result> </action> <action name="goSubmit" class="org.test.submit.action.IsRepeatSubmitAction" method="goSubmit"> </action> </package> </struts>
context_submit.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"> <bean id="submitDaoTarget" class="org.test.submit.dao.impl.SubmitDaoImpl"> <property name="sessionFactory"> <ref bean="sessionFactory"/> </property> </bean> <bean id="submitDao" parent="transactionProxyTemplate"> <property name="target" ref="submitDaoTarget" /> <property name="proxyInterfaces"> <value>org.test.submit.dao.SubmitDao</value> </property> </bean> </beans>
页面;
repeat.jsp
<%@ page contentType="text/html; charset=UTF-8"%> <%@page import="java.util.*"%> <% String path = request.getContextPath(); %> <% String pk_userId=""; if(request.getAttribute("userId")!=null){ pk_userId=(String)request.getAttribute("userId"); } %> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title></title> <script src="<%=path%>/script/jquery-1.7.1.min.js" type="text/javascript"></script> <style type="text/css"> td,th { font-family: 宋体, Arial; font-size: 12px; } th { font: bold 11px "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif; color: #4f6b72; border-right: 1px solid #C1DAD7; border-bottom: 1px solid #C1DAD7; border-top: 1px solid #C1DAD7; letter-spacing: 2px; text-transform: uppercase; text-align: left; padding: 6px 6px 6px 12px; background: #CAE8EA no-repeat; } td { border-right: 1px solid #C1DAD7; border-bottom: 1px solid #C1DAD7; background: #ECFFFF; font-size:11px; padding: 6px 6px 6px 12px; color: #4f6b72; } td.other { border-right: 1px solid #C1DAD7; border-bottom: 1px solid #C1DAD7; background: #FFFFDF; font-size:11px; padding: 6px 6px 6px 12px; color: #4f6b72; } </style> </head> <body style="overflow: scroll; overflow: auto;"> <input type="hidden" name="path" id="path" value='<%=path%>' ></input> <form id="form1" action="<%=path%>/submit/goSubmit.action" method="GET"> <input type="hidden" id="" name="develpoer.userId" value="<%=pk_userId%>"></input> <table id="table1" cellspacing="0" style="width:700px; padding: 0; margin: 0;"> <tr> <th>账号</th> <th>昵称</th> </tr> <tr> <td><input type="text" id="" name="develpoer.userName"></input></td> <td><input type="text" id="" name="develpoer.realName"></input></td> </tr> <tr> <td class="other" colspan="1"><input type="button" value="提交" onclick="goSubmit()"></input></td> <td class="other" colspan="1"><input type="button" value="取消" onclick="clear()"></input></td> </tr> </table> </form> </body> </html> <script type="text/javascript"> function goSubmit(){ document.all.form1.submit(); } function clear(){ $("input['name=develpoer.userName']").val(""); $("input['name=develpoer.realName']").val(""); } </script>
实验:在页面填入数据,点击提交按钮,打印:
主键userId:f6463fb03dbae37b013dbae37b680000
Hibernate: insert into lsy_user_develop(USER_ID, USER_NAME, REAL_NAME) values('f6463fb03dbae37b013dbae37b680000','55','555')
成功写库!Sat Mar 30 18:42:50 CST 2013
成功提交后,按F5刷新页面:
打印:
主键userId:f6463fb03dbae37b013dbae37b680000
您已提交过表单!
或者点击浏览器的回退按钮回到刚才的页面,再点击提交。打印:
主键userId:f6463fb03dbae37b013dbae37b680000
您已提交过表单!
好,从结果上看,这个思路是可行的!
############################-----分割-----################################
从网上搜了一下,struts2有个防止表单重复提交的标签<s: token />,配合拦截器 <interceptor-ref name="token" />来使用。
大体是:
在页面加载时,<s: token />产生一个GUID(Globally Unique Identifier,全局唯一标识符)值的隐藏输入框
同时,将GUID放到会话(session)中;在执行action之前,“token”拦截器将会话token与请求token比较,如果两者相同,则将会话中的token删除并往下执行,否则向actionErrors加入错误信息。如此一来,如果用户通过某种手段提交了两次相同的请求,两个token就会不同。
##################################################
我想这个原理和我想到的解决方案大同小异。只不过struts2把这个写入了标签库和拦截器中。
具体逻辑上strus2根据session是否有这个唯一标识来判断,我根据这个标识的提交次数来判断的。
从实施上,我们完全不必要使用struts2的方案,因为我不喜欢struts2的标签库。只要懂了原理,自己照样能实现。