前后端分离重复提交_前后端分离的情况下表单重复提交的解决方案思考

本文探讨了在前后端分离的场景下,如何解决表单重复提交的问题,提出通过创建表单指纹并设置有效期限来判断是否为重复提交。针对匿名和登录用户,给出了不同的指纹生成方案,并讨论了线程安全的实现方式,包括单节点和多节点(如使用Redis)的解决方案,以及过期指纹的清理策略。
摘要由CSDN通过智能技术生成

约束条件

前后端分离, 无法使用重定向等依赖于浏览器的技术

不对前端有任何要求, 比如说提交表单之前申请一个 Token. 提交之后 disable button 之类的

期望的结果

有效性, 最起码的要求,不能有表单重复提交也不能误报

透明性, 对前端透明, 前端无感知

性能, 当然是越快越好

需要解决的问题

后端怎么判断一个表单重复

已提交的表单应该存储一个指纹(hash)

新的表单应该和已提交的对比, 如果存在就认为是重复提交

怎么给一个表单建立指纹

首先, 按照 REST 接口的标准, GET 或者是 HEAD 方法是没有副作用的, 所以我们只对 POST, DELETE 方法做指纹. 实际项目中其实只用到了 POST, 所以下面的方案都是按照 POST 方法作为说明.

POST 方法数据应该都存储在 Body 中, 最简单的我们可以对 Body 的内容做 hash, 如 hash(body). 但是这种方法有问题, 假如 /endpoint1 和 /endpoint2 提交的数据是一样的, 那么这个指纹就无效了

POST URL 也应该作为 hash 的一部分: hash(url + body). 这种方法也会有问题, 不同用户提交相同的表单会误报

假如这个表单不需要登录就可以提交, 那么我们需要对匿名用户做指纹采集, 最简单的方案就是 User agent 和 IP 地址了, hash(ua + ip + url + body)

假如这个表单需要登录才可以提交, 我们可以直接用用户的 ID 进行 hash: hash(userId + url + body)

表单重复提交的间隔

用户提交一个表单一段时间之后是允许再次提交相同的表单的, 所以指纹记录应该有一个有效期

有效期应该是一个固定的值, 既不能影响用户体验, 也不能误报

实现

实现这个功能是需要注意表单重复提交的危害在于并发问题, 所以实现必须是线程安全的.

定义一下接口

interface FormHashContainer{

// 添加成功之后返回 true, 如果有重复,返回 false

boolean putIfAbsent(Sting hash, Date expireAt)

}

单节点实现

单节点可以使用 hashmap 实现, key 为 hash, value 为过期时间

基本逻辑为:

首先查看 hash 是否存在

如果存在, 检查过期时间, 如果未过期, 返回 false, 如果过期, 更新过期时间, 返回 true

如果不存在, 添加到 hashmap 中, 返回 true

需要解决的问题

线程安全

上述三步操作并非原子操作, 需要保证线程安全

性能

性能不应该影响过大

尝试方案 1: 一把锁

lock.lock()

try{

// step1

// step2

// step3

}finaly{

lock.unlock();

}

缺点很明显, 所有的 POST 请求到这里都会串行, 影响系统并发

尝试方案 2: 读优化

对于绝大多数的请求都是正常的, 非重复提交的, 所以正常请求不应该受到影响.

Date d = hashmap.putIfAbsent(key, value)

if(d == null){

return true;

}else{

lock.lock()

try{

// step1

// step2

// step3

}finaly{

lock.unlock();

}

}

读优化之后性能应该会有所提升, 对于一般的应用也就足够了.

尝试方案 3: 使用更加复杂的数据结构

可以考虑使用类似字典树的数据结构, 但是只有 2 -3 层, 每次只锁一个父节点, 这种数据结构实现起来比较复杂, 实际意义也不大.

关于如果过期指纹的问题

如果长期不进行清理, 那么 hashmap 会越来越大, 所以我们应该有一个过期方案来释放空间

方案 1: 发现重复请求之后进行全局清理

当发现重复请求之后, 会持有锁, 在这个阶段进行清理是线程安全的, 并且重复请求对于用户来说没有什么实际意义, 所以哪怕响应慢一点也无所谓.

方案 2: 后台线程定时清理

后台跑一个线程定时清理, 清理的时候也应该持有锁, 但是对于非重复请求没有任何性能影响.

多节点实现

当然是 redis 了, // todo

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值