● 开发环境:Eclipse+Tomcat+MySQL+SVN
● 系统架构:JQuery+Bootstrap+JFinal+Memcache
概述:防止重复提交的方法有很多,如 执行完方法后重定向到一个提示成功的页面,或者直接把窗口关掉;这些方式稍微搜索一下就出来很多实现,所以这里记录一个响应结果后不重定向也不把窗口关闭,也能防止重复提交的方法;
实现方法:首先页面要有一个隐藏的 async_token_static ,每次跳转加载页面前都会生成一个UUID放到session中,传递到页面给token赋值(不发送请求就不改变),方法提交时会先经过AsynchronousTokenInterceptor拦截器,拦截器对比session和页面token,两者相等时才会放行(此时session中的UUID已被修改,而页面的token不变),否则拦截并提示“重复提交”(如下图1),从而实现不关闭页面防止重复提交;
图1:
1:login登录方法中往session里设置 asyncTokenId
@ControllerAnno(controllerkey="/manager")
public class LoginController extends MyController {
/**
* 方法描述:登陆验证
*/
@Before({UserValidator.class,Tx.class})
@DataSource(type=DataSourceMap.MASTER)
public void login(){
//验证是否有权限访问t系统
Principal principal = this.getRequest().getUserPrincipal();
HttpSession session = this.getRequest().getSession();
this.setSessionAttr("userInfo",user);
this.setSessionAttr("department", dept);
//*************若没有经过防止重复提交的拦截器,则在本次登录中都不会变***************
this.setSessionAttr("asyncTokenId", UUID.randomUUID());
}
2:跳转页面时,先在jsp通用头文件 header.inc 获取session中的 asyncTokenId 赋值到页面的隐藏域中
- header.inc :
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="xh" uri="/WEB-INF/tld/xh.tld"%>
<%@ taglib prefix="ex" uri="http://www.xiaohe.com/tag"%>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
<%
String contextPath = request.getContextPath();
%>
<%
String projectRoot = contextPath;
%>
<%
String cachePath = contextPath;
%>
var contextPath = "<%=contextPath%>";
var cachePath = "<%=cachePath%>";
//***********先获取session中的asyncTokenId(实测不用加 ${sessionScope.asyncTokenId})**************
var async_token_static = "${asyncTokenId}";
var sync_token_from_session = function (asyncTokenId) {
async_token_static = asyncTokenId;
$("input[name='async_token_static']").attr("value", asyncTokenId);
};
$().ready(function(){
var mask = $('<div id="loadingMask" style="display:none;position:fixed;_position:absolute;top:0;left: 0;width:100%;height:100%;background :#ffffff;z-index:100;filter:alpha(opacity=0);opacity:0.3;"></div>');
var loading = $('<div id="loadingContent" class="loading mt50 tc " style="display:none;position: fixed;width:100%;text-align: center;top: 20%;z-index: 9999999"><p class="mt80 p tc"> <img src="<%=contextPath %>/res/img/wait.gif" alt=""></p><p style="font-size: 20px;font-weight:bold;" id="loadingText">请稍等,加载中...</p></div>');
document.body.appendChild(mask[0]);
document.body.appendChild(loading[0]);
initSelect();
if ($("input[name='async_token_static']").
length > 0) {
$("input[name='async_token_static']").attr("value", async_token_static);
}
});
- toPlanClassRoom.jsp
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%@include file="/WEB-INF/jsp/include/header.inc"%>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html lang="zh-CN">
<head>
<title>时间模板分页列表</title>
<script type="text/javascript">
function planRoom(id,name){
var url=cachePath+"/adminClass/doPlanClassRoom";
//**************提交请求时带上token值******************
var data="curriculum.currClaId=${claId}&curriculum.currRoomId="+id+"&curriculum.currRoomName="+name+"&async_token_static="+$("input[name='async_token_static']").val();
$.post(url,data,function(json){
if(json.jumpType){
alert("添加成功!");
window.top.currDialog.parent.q_search("schedule_tab");
window.top.currDialog.close();
}else{
var $p = $("<p></p>");
var $table = $("<table class='table table-striped table-bordered table-hover' style='overflow-y: scroll;'>");
$p.append($table);
$table.append("<thead><th>冲突详情</th></thead>");
var $tbody = $("<tbody></tbody>");
var conflicts = json.returnMessage.split("_");
if(conflicts.length >0){
$.each(conflicts,function(i,n){
var vals = n.split("@");
if(vals.length > 1){
var $tr = $("<tr><td style='padding-left: 30px !important;'><span onclick=\"alert('"+vals[1]+"')\" class='teaNameClass'> "+vals[0]+" </span></td></tr>");
}else{
var $tr = $("<tr><td style='padding-left: 30px !important;'>"+n+"</td></tr>");;
}
$tbody.append($tr);
})
$table.append($tbody);
}
$.xhModalAlert({
title: "添加失败",
type:'alert',
width: 600,
context: $p.html(),
buttons:[{label:'取消',clickEvent:function(dialog,iframe){dialog.close();}}]
});
}
}, "json" );
}
</script>
</head>
<body class="bodycss">
......
<%--*********************赋值的token*************************--%>
<input type="hidden" name="async_token_static" />
......
</body>
</html>
3:提交该请求后会先经过 AsynchronousTokenInterceptor 拦截器对比当前请求和session中的token值,相同才会放行,不同则判断是重复提交;
- 比对通过后,会更新session中的asyncTokenId值,而页面的token值还是不变,从而防止重复提交
public class AsynchronousTokenInterceptor implements Interceptor {
private Logger logger = Logger.getLogger(AsynchronousTokenInterceptor.class);
public AsynchronousTokenInterceptor() {
}
//改为同步方法,避免重复提交
public synchronized void intercept(ActionInvocation ai) {
try {
Controller controller = ai.getController();
HttpServletRequest request = controller.getRequest();
String paraAsyncTokenId = controller.getPara("async_token_static").toString();
String sessionAsyncTokenId = controller.getSessionAttr("asyncTokenId").toString();
//******************比对当前请求和session中的token值***********************
if (paraAsyncTokenId.equals(sessionAsyncTokenId)) {
//***************如果比对相等,就更新session中的asyncTokenId值****************
controller.setSessionAttr("asyncTokenId", UUID.randomUUID());
this.logger.info("--------------提交!");
ai.invoke();
} else {
if (request.getHeader("x-requested-with") != null && request.getHeader("x-requested-with").equalsIgnoreCase("XMLHttpRequest")) {
XhJSONResult result = new XhJSONResult(Boolean.FALSE.booleanValue(), "重复提交了!", "", paraAsyncTokenId);
controller.renderJson(result);
}
this.logger.info("--------------重复提交了!");
}
} catch (Exception var7) {
var7.printStackTrace();
}
}
}
- AsynchronousTokenInterceptor 拦截器起作用也是通过在方法上贴上注解:
@ControllerAnno(controllerkey = "/adminClass")
@Before({LoginInterceptor.class,DateJurisdictionInterceptor.class})
public class AdminClassController extends MyController {
private IClassService classService = new ClassService();
@DataSource(type = DataSourceMap.MASTER)
@Before({ AsynchronousTokenInterceptor.class, Tx.class }) //****需要拦截重复提交的方法上贴
public void doPlanClassRoom() {
try {
Users user = (Users) this.getUserInfo();
Curriculum curr = this.getModel(Curriculum.class);
String message = classService.doPlanClassRoom(curr, user,0);
renderJsonMsgResult(message, null);
} catch (Exception e) {
this.renderSystemError(e);
}
}