我正在使用 '导轨','~> 5.2.4','>= 5.2.4.1' 红宝石“2.7.1” 和 gem 'pg', '>= 0.18', '< 2.0'
我在 Rails 应用程序中遇到一个问题,某些用户在数据库中被重复。此问题间歇性发生,似乎与用户创建操作的重复请求有关,导致同时创建两个用户记录,尽管已实施验证以确保电子邮件和 CPF 等字段的唯一性。
分析 Elastic Search 中的日志,我发现用户创建操作日志有两个重复条目,表明重复发生在请求级别。
在我的客户模型中,我已经对电子邮件和 CPF 等字段实现了唯一性验证,使用white_label_origin 字段的范围来确保仅在正确的上下文中检查唯一性。
CustomersController#create
def create
payload = {
created_at: DateTime.now,
name: 'Customer Controller',
type: 'log',
event: 'create - Customer',
session_id: session.id.to_s,
general_text: 'Create customer'
}
log_elastic(payload)
@customer = Customer.new(customer_params.merge(require_full_signup: true)
.merge(white_label_data: white_label_session))
if @customer.save
payload = {
created_at: DateTime.now,
name: 'Customer Controller',
type: 'log',
event: 'saved - Customer',
customer_id: @customer.id,
session_id: session.id.to_s,
general_text: 'customer saved'
}
log_elastic(payload)
end
end
在客户模型中实施的验证:
validates :email, uniqueness: {
scope: :white_label_origin,
message: 'Email already registered'
}
validates :cpf, uniqueness: {
scope: :white_label_origin,
message: 'Document already registered'
}, on: :create, unless: :omniauth_customer?
我已经搜索过它并找到了这篇文章,它解释了我需要使用索引,所以我在迁移中发现了这个:
class AddIndexToCustomerEmailAndCpf < ActiveRecord::Migration[5.2]
def change
add_index :customers, :email
add_index :customers, :cpf
end
end
我想了解导致应用程序中用户重复的原因以及如何解决此问题以防止将来再次发生。造成这种行为的可能原因是什么?我该如何实施有效的解决方案来确保用户的独特创建?
这是人们双击提交表单时发生的常见竞争条件。
双击导致浏览器向Web服务器发送两个请求。并且两个请求同时由两个不同的服务器进程或线程处理。两个进程同时检查用户是否已经存在,都得到这样的用户还不存在的答案。然后两个进程都会创建用户。
这意味着模型中的唯一验证只能在某种程度上保护您免受重复记录的影响,并且当几乎同时存在重复请求时会失败。
为了确保任何时候都不存在重复记录,您还需要为这些属性添加唯一索引到数据库中。这意味着,验证可以保护您的常见用例,并允许向用户返回正确的错误消息。在应用程序无法捕获问题的情况下,数据库中的唯一索引可以保护您的数据。
向数据库添加索引:
add_index :customers, :email, unique: true
处理控制器中可能出现的错误(验证和唯一约束违规):
def create
# [...]
@customer = Customer.new(
customer_params.merge(
require_full_signup: true, white_label_data: white_label_session
)
)
if @customer.save
# Customer was successfully created.
else
# validating the customer failed, see `@customer.errors` for details
end
rescue ActiveRecord::RecordNotUnique => exception
# Record creation failed because of the unique index in the database.
# Depending on your usecase returning the existing customer with
#
# @customer = Customer.find_by(email: @customer.email)
#
# might be an option to fix the issue.
end