现在,如果我非常快速地粉碎这个“卖出”按钮,以便html没有时间更新,我可以不断出售相同的项目并每次获得积分,我该如何防止这种情况?
用户模型的方法:
has_many :drivers
def withdraw(amount)
balance = self.credit
if balance >= amount
new_balance = balance - amount
self.update credit: new_balance
true
else
false
end
end
def deposit(amount)
balance = self.credit
balance += amount
self.update credit: balance
end
def purchase(package)
cost = package.cost
ActiveRecord::Base.transaction do
self.withdraw(cost)
package.update user_id: self.id
end
end
def sell(package)
cost = package.cost
ActiveRecord::Base.transaction do
self.deposit(cost)
package.update user_id: nil
end
end
使用买入/卖出按钮查看:
<% unless @driver.owned? %>
<%= button_to "Buy", purchase_driver_path %>
<% else %>
<%= button_to "Sell", sell_driver_path, method: :delete %>
<% end %>
我的控制器
class DriversController < ApplicationController
def show
@user = current_user
@driver = Driver.find(params[:id])
end
def purchase
@driver = Driver.find(params[:id])
@user = current_user
if @user.purchase(@driver)
flash[:succes] = "Purchase succesful!"
else
flash[:error] = "Error"
end
render "show"
end
def sell
@driver = Driver.find(params[:id])
@user = current_user
if @user.sell(@driver)
flash[:succes] = "Sell succesful!"
else
flash[:error] = "Error"
end
render "show"
end
end
谢谢!
答案 0 :(得分:1)
最简单的方法可能是在sell
方法中插入一个保护条款(目前,package
当前属于用户并不重要。)
def sell(package)
return unless package.user == self
... # your original method here
end
这样,除非满足初始条件,否则该方法将不执行任何操作。
答案 1 :(得分:1)
您应该在模型层上处理此问题,以便您知道其他任何代码都无法利用此竞争条件。
做两件事:
1)为重要的无效状态逻辑添加模型验证,例如
class Driver
validate :validate_not_previously_purchased, on: :purchase
def validate_not_previously_purchased
if user_id && user_id_change[0] != nil
errors.add(:user_id, 'a user has already purchased this product')
end
end
end
2)在锁定交易中使用验证
class User
...
def purchase(package)
ActiveRecord::Base.transaction(lock: true) do
package.user_id = self.id
package.save(context: :purchase)
self.withdraw(package.cost)
end
end
end
交易&amp;如果验证失败,验证会使其回滚,并且锁定确保多个用户在最后一次胜利的竞争条件下不会“购买”包。
您还需要考虑以相同方式验证用户信用的业务逻辑,以便如果购买交易没有足够的信用等,也会失败。
这是很好的机会,用于对代码的超级关键部分进行单元测试。
最后,提醒一下保持锁中逻辑量尽可能小的警告。锁定既是数据库的主要特征,也是大型应用程序扩展问题的祸根。只要你把东西保持在快速锁定状态光(它现在是),它会很好。
答案 2 :(得分:0)
除了Zach的方法之外,您还可以添加一些客户端JavaScript以在点击后立即禁用该按钮。我必须在一个表单上传文档的应用程序中执行此操作,这使得向用户提供反馈的速度很慢(即,删除表单页面等)。
// CoffeeScript
$('.button-class').click (e) ->
$(@).attr('diabled', 'disabled')
或者(这个恰好是JS ......抱歉,从两个文件中获取代码示例:)) -
$('.form-button').submit(function(e) {
$(this).find('input[type=submit]').attr('disabled', 'disabled');
});
这两种方法对我有用,具体取决于我是否需要提交或单击处理程序中的操作。