0

在我的应用程序中,工作人员可以在他们的帐户中建立贷方余额,并在他们认为合适的情况下将其退回到他们的银行帐户。有一个MoneyController::withdraw动作,在current_worker上调用.withdraw_funds方法,该方法调用平衡支付API,如果他们要求提取的amount为< =他们余额中的金额,则贷记他们的银行账户。创建一个Transaction,附加到工作人员Account,该清单列出从余额中扣除并记入他们的银行帐户的金额。(Rails)有些用户在从余额中提取钱到银行账户时获得两次付款。无法复制

最近发生了一些情况,即控制器操作受到影响并且整个过程发生两次,即使请求在余额为空时应该被拒绝。撤销发生两次,两个事务使用相同或非常接近的时间戳生成。但是,它只发生在一些工作人员身上,我无法在开发或登台服务器上重现错误。我希望有人可以给我一些关于如何进行调试的建议。下面是相关代码:

MoneyController 
    def withdraw 
    if current_worker.withdraw_funds((params[:amount].to_d*100).to_i, params[:bbank]) 
     redirect_to worker_money_path, notice: "Successfully withdrew $#{params[:amount]}" 
    else 
     redirect_to worker_money_path, alert: "Failed to withdraw funds. Please contact us for assistance." 
    end 
    end 

/

worker.rb 

    def withdraw_funds(amount, bbank_id) 
    bcust = self.get_balanced 
    bbank = Balanced::BankAccount.fetch("/bank_accounts/#{bbank_id}") 
    if bbank and (bbank.customer.id == bcust.id) 
     puts "bank belongs to worker" 
     if self.account.balance >= amount 
     res = bbank.credit(amount: amount) 
     self.account.debit(amount) 
     Transaction.create(amount: amount, tag: 'cashout', source_id: self.account.id, destination_id: nil, balanced_id: res.id) 
     return true 
     else 
     puts "worker #{self} doesn't have #{amount} in account" 
     return false 
     end 
    else 
     puts "bank does not belong to worker" 
     return false 
    end 
    end 

如果员工的平衡包含$ 50和两个请求为50 $做,首先应该成功,那么第二个要失败,因为天平现在$ 0(因此if self.account.balance >= amount)。

我能够通过开发服务器的日志看起来很好,找到这种情况发生时的日志:我在这两个请求具有相同的真实性令牌,但我不是日志注意到

Started POST "/money/withdraw" for 68.119.221.188 at 2014-11-05 13:56:41 -0500 
Processing by MoneyController#withdraw as HTML 
    Parameters: {"utf8"=>"✓", "authenticity_token"=>"x5olIpvJf2K37lYRJypIIHYNhAdZUm1ptill13w9Evw=", "amount"=>"48.50", "bbank"=>"BA2...", "commit"=>"Withdraw"} 
Started POST "/money/withdraw" for xx.xxx.xxx.xxx at 2014-11-05 13:56:46 -0500 
Processing by MoneyController#withdraw as HTML 
    Parameters: {"utf8"=>"✓", "authenticity_token"=>"x5olIpvJf2K37lYRJypIIHYNhAdZUm1ptill13w9Evw=", "amount"=>"48.50", "bbank"=>"BA2...", "commit"=>"Withdraw"} 
Redirected to http://myapp.com/money 
Completed 302 Found in 4467.9ms (ActiveRecord: 54.7ms) 
Started GET "/worker/money" for xx.xxx.xxx.xxx at 2014-11-05 13:56:50 -0500 
Processing by Worker::MoneyController#index as HTML 
Redirected to http://myapp.com/money 
Completed 302 Found in 9099.1ms (ActiveRecord: 69.6ms) 

当然还有什么要从他们身上带走。我认为这可能与工作人员多次单击“提款”按钮一样简单,但是我试图在开发和分期中重新创建问题,从未造成问题。请求刚刚排队,随后的请求始终引起余额为空的正确响应。

编辑我设置在生产服务器上测试场景,并能够通过点击按钮退出多次重现该问题。任何人都有任何想法,为什么这会发生在生产,但不在发展或阶段?连接速度可能与它有什么关系?

+0

嗨。请求是由ajax还是通常由javascript启动的? – 2014-11-05 22:17:10

+0

不,这是一个HTML请求 – sixty4bit 2014-11-05 22:22:18

+0

您可以粘贴表单的html,如果有任何现有的相对事件处理程序(在JavaScript或jQuery中)? – 2014-11-05 22:24:25

回答

1

既然你不张贴的客户端代码,我可以建议你把一些类型的锁,以Worker使他只能有一次在一个运行的事务......像in_process:booleanprocess_start:timestamp(两者合并)。

所以,当你开始交易时,您确保in_processfalse并将其设置为true,还修改process_start(放在那里意外锁,例如,假设主动锁定,如果in_process && process_start>(Time.now - 10.minutes)

该过程完成后,您将in_process标志设置为false。

这样,每个用户只能有一个进程处于活动状态。

当然,如果我们有一些html,这可能不是必需的,但最好是在业务(控制器)逻辑背后涉及钱的流程。

编辑:也许这也是一个好主意,让Worker在交易结束后锁定几秒钟。

+0

感谢您的建议。我会给它一个镜头。我目前的解决方案就是在点击后使用javascript来禁用按钮,但正如您指出的那样,前端限制并不像执行某种控制器逻辑那样安全。我从来没有听说过这些进程/事务锁。你知道我可以阅读更多信息的任何文档吗? – sixty4bit 2014-11-06 04:20:29

+0

我的答案是自定义逻辑,但是您可以搜索谷歌“rails事务锁定”来查找内容...但据我所知,现有的解决方案都没有提供简短的提交后锁定时间。你可以使用例如活动记录的'lock'方法或'transaction'块 – 2014-11-06 05:00:20