● 开发环境:Eclipse+Tomcat+MySQL+SVN
● 系统架构:JQuery+Bootstrap+JFinal+Memcache

概述:防止重复提交的方法有很多,如 执行完方法后重定向到一个提示成功的页面,或者直接把窗口关掉;这些方式稍微搜索一下就出来很多实现,所以这里记录一个响应结果后不重定向也不把窗口关闭,也能防止重复提交的方法;

实现方法:首先页面要有一个隐藏的 async_token_static ,每次跳转加载页面前都会生成一个UUID放到session中,传递到页面给token赋值(不发送请求就不改变),方法提交时会先经过AsynchronousTokenInterceptor拦截器,拦截器对比session和页面token,两者相等时才会放行(此时session中的UUID已被修改,而页面的token不变),否则拦截并提示“重复提交”(如下图1),从而实现不关闭页面防止重复提交;

图1:

java token避免重复充值_ci

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);
        }
    }