避免重复提交(幂等性处理)

什么是重复提交

提交:你打开个网页,要注册个邮箱,你点击注册按钮后成功了,就是一次成功的数据提交。
重复提交:相当于在点击了一次注册按钮,但是因为网络波动,页面没有响应,此时你又点击了一次注册按钮(多次点击)。网络波动恢复,但是此时给后台传输的数据是两条一样的数据。如果不进行处理,将会以同样的用户数据注册了两个账号。

危害性:就像你买东西给某人转账,确定转 200元,但是因为手机网络信号不好,支付按钮点了一次没用。你有点疑惑,继续点了点,还是没反应。逐渐暴躁,点了多次按钮。突然,网络恢复正常,你发现本来只想转一次 200,变成转了 200次 200。同时那个人收款后直接把你拉黑了。。。。。。真实的令人发指

防止重复提交,就是实现幂等性:在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同
一个转账场景下,点击了一次转账跟点击了多次转账按钮效果一样,只转一次

如何防止重复提交

  • 前端控制
    • 控制按钮:按钮点击一次后,再未接受到后台返回值之前,不可再点击
    • 页面跳转:点击按钮后,直接跳转到一个静态页面。(也是控制按钮的不可再点击)
    • 提交数据识别:对于按钮提交的参数进行识别,看是否是相同的参数
  • 后台控制

前端控制实现

/**
* 全局属性,用于保存提交参数的 hashCode。起到缓存的作用
*/
watcher: {},
    
/**
* ajax 统一调用接口
* jsonData:传递给后台的的参数
*/
JsonRequest(jsonData) {
    let me = this, e = $Defer(), w = this.watcher
    // 对于参数进行 hashCode 生成唯一标识码保存在全局属性中。
    const uniqueId = this.hashcode(jsonData)
    // add by YangL 前端防重提交控制
    if (w[uniqueId]) {
        e.reject({msg: "服务器正在处理您的请求,请稍后...^_^"});
        return e.promise;
    }
    w[uniqueId] = true;
    // 进行 ajax 异步处理(自定义方法)
    me.RequestAsync(jsonData).then(function (n) {
        // 后台返回数据
        w[uniqueId] = false
        e.resolve(n)
    }, function (n) {
        // 后台fan'hui'shu'j
        w[uniqueId] = false
        e.reject(n)
    })
    return e.promise;
},

后台控制实现

通过token

服务器端判断客户端提交上来的 Token 与服务器端生成的 Token 是否一致,如果不一致,那就是重复提交了。

public class IdempotentAspect implements MethodInterceptor, ThrowsAdvice {
    private static final Logger log = LoggerFactory.getLogger(IdempotentAspect.class);
    private static final String SIGN_RUNNING = "running";
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        Idempotent idempotent = (Idempotent)methodInvocation.getMethod().getAnnotation(Idempotent.class);
        HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
        String idempotentToken = request.getHeader("idempotent_token");
        if (S.isEmpty(idempotentToken)) {
            throw new AccessDeniedException("请求被拒绝,请求头参数[idempotent_token]不能为空!");
        } else {
            idempotentToken = "rbmh_idemp:token:" + idempotentToken;
            if (!RedisUtils.hasKey(idempotentToken)) {
                throw new AccessDeniedException("请求被拒绝,请求头参数[idempotent_token]不存在或已过期!");
            } else {
                String responseText = (String)RedisUtils.get(idempotentToken);
                if (responseText == null) {
                    responseText = (String)RedisUtils.getAndSet(idempotentToken, "running");
                    if (responseText != null) {
                        return this.reject(idempotentToken, responseText, idempotent);
                    } else {
                        Object response = methodInvocation.proceed();
                        this.resolve(idempotentToken, response);
                        return response;
                    }
                } else {
                    return this.reject(idempotentToken, responseText, idempotent);
                }
            }
        }
    }
}

posted @ 2024-08-09 09:37  平安QAQ  阅读(5)  评论(0编辑  收藏  举报