重複送信を解決するJavaバックエンド問題(、最初の問題を知るためではありません)

序文

发生条件:
    接受到一个请求,该请求没有执行完成又接受到相同请求,导致数据错误(如果是前一个请求执行完成,马上又接受相同请求不会有问题) 

问题分析:会产生数据库脏读 

治療の選択肢

客户端:
    表单提交后将提交按钮设为disable等

服务端:
    唯一Token值与提交参数相匹配验证

1. 添加拦截器,拦截需要防重复提交的请求

2. 通过注解@Token来添加token/移除token

3. 前端页面表单添加(如果是Ajax请求则需要在请求的json数据中添加token值)


/**
 * 拦截器
 */
public class TokenInterceptor extends HandlerInterceptorAdapter {

    static String splitFlag = "_";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();

            //判断是否注解@Token
            Token annotation = method.getAnnotation(Token.class);
            if (annotation == null) {
                return true;
            }
            
            //判断是否需要添加Token
            boolean needSaveSession = annotation.add();
            if (needSaveSession) {
                //设置Token
                Random random = new Random();
                String uuid = UUID.randomUUID().toString().replace(splitFlag, String.valueOf(random.nextInt(100000)));
                String currentTime = String.valueOf(System.currentTimeMillis());
                request.setAttribute("token", uuid + splitFlag + currentTime);
                //request.getSession(true):若存在会话则返回该会话,否则新建一个会话。
                request.getSession(true).setAttribute(uuid, currentTime);
            }

            //判断是否需要添移除Token
            boolean needRemoveSession = annotation.remove();
            if (needRemoveSession) {
                //判断是否重复提交,重复提交则拦截
                if (isRepeatSubmit(request)) {
                    logger.warn("please don't repeat submit,url:" + request.getServletPath());
                    return false;
                }

                //非重复提交,从Session中移除Token
                String token = request.getParameter("token");
                if (token != null) {
                    request.getSession(true).removeAttribute(token.split("_")[0]);
                }
            }
            return true;
        } else {
            return super.preHandle(request, response, handler);
        }
    }

    /**
     * 判断是否是重复提交
     */
    private boolean isRepeatSubmit(HttpServletRequest request) {

        String token = request.getParameter("token");

        if (token == null) {
            return true;
        }
        String uuidToken = token.split("_")[0];
        String currentTimeToken = token.split("_")[1];
        String currentTime = (String) request.getSession(true).getAttribute(uuid);
        if (currentTime == null) {
            return true;
        }
        if (!currentTime.equals(currentTimeToken)) {
            return true;
        }

        return false;
    }
}


/**
 * 注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Token {

    /**
     * 添加token的开关[true:添加;false:不添加。default:false]
     */
    boolean add() default false;

    /**
     * 移除token的开关[true:删除;false:不删除。default:false]
     */
    boolean remove() default false;

}



<mvc:interceptors>
    <!-- 配置Token拦截器,防止用户重复提交数据 -->
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <bean class="interceptor.TokenInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>


使用

1.在进入页面的controller方法上添加注解@Token(add=true)

@Token(add = true)
@RequestMapping("/find")
public String find(Model model) {
   ......
   return "list";
}


2.在页面list.html添加

<form id="form" class="form-horizontal" action="/add" method="post">
   <!-- 注:name必须是token -->
   <input type="hidden"  name="token" value="${token!''}"/>
</form>


3.在防止重复提交的controller方法上添加注解@Token(remove = true)

@Token(remove = true)
@RequestMapping("/add")
public String add() throws Exception {
   ......
   return "redirect:/find";
}

おすすめ

転載: www.cnblogs.com/loveer/p/11585324.html