面试时遇到一个题:怎么防止表单重复提交?
当时想了想,这个题不是很难,简单来说就是验证的问题。于是我很容易想到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="提交" οnclick="goSubmit()"></input></td>
<td class="other" colspan="1"><input type="button" value="取消" οnclick="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的标签库。只要懂了原理,自己照样能实现。