为何Rails应用中点击一次下载按钮,download_file方法被调用两次?
解决Rails下载方法被重复调用导致积分重复扣减的问题
可能的原因及解决办法:
浏览器预取/预加载触发重复请求
部分现代浏览器会自动预取看起来是下载或导航的GET链接,导致请求被触发两次。可以给下载链接添加属性阻止预取:<%= link_to "Download", download_file_path, class: "download-button", rel: "noopener noreferrer nofollow", data: { no_prefetch: true } %>改用POST请求替代GET
GET请求天生容易被缓存、预取,换成POST能从根源避免浏览器主动触发重复请求:- 修改路由:
# routes.rb post 'download_file', to: 'dashboard#download_file', as: 'download_file' - 修改前端链接(用button_to或给link_to加method属性):
或<%= button_to "Download", download_file_path, class: "download-button", method: :post %>
注:使用link_to的method属性需要确保项目中引入了Rails UJS(application.js里有<%= link_to "Download", download_file_path, class: "download-button", method: :post %>//= require rails-ujs)
- 修改路由:
添加幂等性校验,确保操作只执行一次
即使请求被重复触发,也要通过数据库层面的控制保证积分只扣减一次,推荐使用乐观锁:- 给accounts表添加lock_version字段(整数类型,默认值0):
rails generate migration AddLockVersionToAccounts lock_version:integer rails db:migrate - 修改控制器代码,用事务+乐观锁包裹扣积分逻辑:
# dashboard_controller.rb def download_file puts "download_file method called" current_account.with_lock do if current_account.credit < 1 redirect_to root_path, notice: "You don't have enough credits to download this file." return end puts current_account.credit current_account.credit -= 1 current_account.save! end file = File.open("#{Rails.root}/test_file.zip", "r") self.response.headers["Content-Type"] = "application/zip" self.response.headers["Content-Disposition"] = "attachment; filename='test_file.zip'" self.response_body = file end
这样即使两次请求同时到达,只有一个能成功提交事务,另一个会因乐观锁冲突失败,避免重复扣积分。
- 给accounts表添加lock_version字段(整数类型,默认值0):
检查前端是否重复绑定事件
排查页面JS代码,确认是否不小心给下载按钮绑定了两次click事件。比如避免重复使用$('.download-button').on('click', ...),可以先解绑再绑定:$('.download-button').off('click').on('click', function(e) { // 你的逻辑 });
内容的提问来源于stack exchange,提问作者Ali Ove
相关产品推荐
相关产品推荐

