java+react前后端分离项目处理重复提交问题

Posted 一脸沧桑的刘先生

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java+react前后端分离项目处理重复提交问题相关的知识,希望对你有一定的参考价值。

重复提交的问题在web开发中是很常碰到的一个问题,主要分为前端和后端两种途径解决,前端处理一般采用提交事件后,禁止用户再次点击提交按钮,等待服务端结果再重置提交按钮状态。

本文着重介绍,通过java后端处理重复提交问题。开发环境是:spring boot 2.0+react+ant+dva,下图是主要流程思路:

 

以下是详细步骤代码:

1:客户端登陆,服务端登陆成功后返回初始的表单令牌

package com.df.web.manager.security;


import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;

/**
 * @类名称:
 * @类描述:
 * @创建人 刘丹
 * @创建时间 2018/6/23
 * @最后修改人 刘丹.
 * @最后修改时间 2018/6/23.
 * @版本:1.0
 */
public class FormTokenUtil {
    public static String refreshFormToken(HttpServletRequest request, HttpServletResponse response) {
        String newFormToken = UUID.randomUUID().toString();
        response.setHeader("formToken", newFormToken);
        request.getSession(true).setAttribute("formToken", newFormToken);
        return newFormToken;
    }
}

2:前端获取服务端返回的formToken

            sessionStorage.setItem("formToken", resData.result.formToken);

3:在前端统一的request(fetch)的headers中增加表单token项

    return request(serviceUrl,
        {
            method: "POST",
            headers: {
                \'Accept\': \'application/json\',
                \'Content-Type\': \'application/json\',
                \'formToken\': sessionStorage.getItem("formToken")
            },
            body: data,
            credentials: \'include\'
        });

4:服务端使用aop技术拦截指定注解的Controller请求

package com.df.web.manager.aop;

import com.df.web.manager.security.FormTokenUtil;
import com.empiresoft.annotation.FormToken;
import com.empiresoft.pojo.common.ActionResultGenerator;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @类名称: 表单重复提交拦截处理
 * @类描述:
 * @创建人 刘丹
 * @创建时间 2018/6/23
 * @最后修改人 刘丹.
 * @最后修改时间 2018/6/23.
 * @版本:1.0
 */
@Aspect
@Component
public class FormTokenAspect {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 对formToken注解的Action执行重复提交验证
     *
     * @param proceedingJoinPoint
     * @param formToken
     * @return
     */
    @Around("@annotation(formToken)")
    public Object execute(ProceedingJoinPoint proceedingJoinPoint, FormToken formToken) {
        try {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            HttpServletResponse response = attributes.getResponse();
            String strFormToken = request.getHeader("formToken");
            if (strFormToken == null) {
                return ActionResultGenerator.errorResult("表单Token不能为空!");
            }
            Object sessionFormToken = request.getSession(true).getAttribute("formToken");
            if (sessionFormToken == null || !sessionFormToken.toString().equals(strFormToken)) {
                return ActionResultGenerator.errorResult("请勿重复提交数据!");
            }
            //放行
            Object o = proceedingJoinPoint.proceed();
            //重置表单令牌 且写入response 重置前端 表单令牌
            FormTokenUtil.refreshFormToken(request, response);
            return o;
        } catch (Throwable e) {
            logger.error(e.getMessage());
            return ActionResultGenerator.errorResult("发生异常!");
        }
    }
}

5:前端监控Response返回的数据中是否包含表单token项,如果包含则重置前端sessionStorage的表单token。

import fetch from \'dva/fetch\';
import { message } from \'antd\';


function parseJSON(response) {
  if (response.headers.get("formToken")) {
    sessionStorage.setItem("formToken", response.headers.get("formToken"))
  }
  return response.json();
}

function checkStatus(response) {
  if (response.status >= 200 && response.status < 300) {
    return response;
  }
}

/**
 * Requests a URL, returning a promise.
 *
 * @param  {string} url       The URL we want to request
 * @param  {object} [options] The options we want to pass to "fetch"
 * @return {object}           An object containing either "data" or "err"
 */
export default function request(url, options) {
  return fetch(url, options)
    .then(checkStatus)
    .then(parseJSON)
    .then(data => ({ data }))
    .catch((err) => {
    });
}

 注解定义:

package com.empiresoft.annotation;

import java.lang.annotation.*;

/**
 * @类名称:FormToken注解类
 * @类描述:使用此注解 则表示需要验证FormToken, 用于处理表单重复提交
 * @创建人 刘丹
 * @创建时间 2018/6/23
 * @最后修改人 刘丹.
 * @最后修改时间 2018/6/23.
 * @版本:1.0
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface FormToken {

}

标记需要重复提交验证

   @FormToken
    @RequestMapping(value = "/call_service", method = RequestMethod.POST)
    public ActionResult callServiceByPost(@RequestBody CallService callService) throws Exception {
        return OauthClientUtil.callUnifiedPlatformService(callService, SecurityUtil.getLoginUser(request), request);
    }

 

注:如需允许用户不同的表单使用不同的表单token,只对同性质表单做重复提交验证,可在前后端对token名称"formToken"的命名做扩展处理。

以上是关于java+react前后端分离项目处理重复提交问题的主要内容,如果未能解决你的问题,请参考以下文章

SpringBoot + React 前后端分离多模块项目框架搭建流程

前后端分类,数据传输问题

React项目配置6(前后端分离如何控制用户权限)

利用Spring boot+react快速搭建一个博客站点(前后端完全分离)

前后端分离及React的一些研究

Java网络商城项目 SpringBoot+SpringCloud+Vue 网络商城(SSM前后端分离项目)四(微服务搭建-通用工具类-通用异常处理-自定义异常处理)