1

我遇到了人们双击我的表单提交按钮并提交多次的问题,所以我只想允许一次提交。我的第一个想法是 javascript,但这不是绝对瞬时的,我想要 100% 保证工作的东西。

我的解决方案是使提交具有幂等性,通过给每个表单加载自己的哈希,并在每次提交时检查该哈希是否已经存在,如果存在,则不做任何事情。

所以这是我的代码的要点:

#form
@hash = SecureRandom.urlsafe_base64(20)
f.hidden_field :hash, value: @hash
f.submit "submit"

#controller
existing = Submission.find_by(hash: params[:hash])
if existing.nil?
  #enter new Submission into the database with hash: params[:hash]

如果我在提交之间留出一些时间,这会起作用,但是当我双击提交按钮时,两条记录会输入到我的数据库中,具有相同的哈希值。

我在本地主机上使用一个简单的 Puma 3.7 服务器。我的印象是大多数服务器会收到一个请求,执行它,然后继续下一个请求,但这几乎就像是在进行某种类型的并行。

我的问题是:这怎么可能?每个后续记录的 ID 值都比前一个记录大一,因此服务器并不知道前一个记录。那么,如果请求发送得非常快,那么如何忽略唯一哈希要求呢?同样,如果我稍后再尝试使用相同的哈希,则不会发生任何事情,正如预期的那样。

4

1 回答 1

1

只需使用Rack::Throttle而不是重新发明轮子。

# config/application.rb
require 'rack/throttle'

class Application < Rails::Application
  config.middleware.use Rack::Throttle::Interval
end

这在来自同一客户端的请求之间应用了 1 秒的最小间隔。您可以使用该min:选项对其进行自定义。

这可以与javascript debounce/throttling结合使用,这将进一步减少您的服务器(和客户端)上的负载。

我在本地主机上使用一个简单的 Puma 3.7 服务器。我的印象是大多数服务器会收到一个请求,执行它,然后继续下一个请求,但这几乎就像是在进行某种类型的并行。

Puma 是多线程的。虽然您可以将其配置为单线程,但 Ruby 主要是阻塞 IO,因此单线程 Web 服务器在负载下表现不佳。

我的问题是:这怎么可能?每个后续记录的 ID 值都比前一个记录大一,因此服务器并不知道前一个记录。那么,如果请求发送得非常快,那么如何忽略唯一哈希要求呢?

比赛条件。您正在做的是应用程序级别的唯一性验证,您可以在其中对数据库运行查询,然后在数据库中插入一行。

如果两个(或更多)进程同时响应重复请求,则两者都将通过Submission.find_by(hash: params[:hash])查询获得绿灯,然后继续插入记录。

这是任何语言/框架中的应用程序验证的一个众所周知的问题,可以通过在数据库中添加唯一索引来解决,这将导致数据库拒绝重复的插入语句。这将在 Rails 中引发数据库驱动程序错误。

但我仍然认为这应该在中间件级别处理。

于 2020-02-15T12:49:30.777 回答