【发布时间】:2013-04-20 18:25:28
【问题描述】:
我目前正在对我正在为 Android 制作的游戏进行现场测试。这些服务是用 rails 3.1 编写的,我使用的是 Postgresql。我的一些更精通技术的测试人员已经能够通过记录他们对服务器的请求并以高并发重放它们来操纵游戏。我将尝试简要描述下面的场景,而不会陷入代码中。
- 一个用户可以购买多件商品,每件商品在数据库中都有自己的记录。
- 请求转到控制器操作,该操作创建购买模型以记录有关交易的信息。
- 贸易模型有一个方法来设置物品的购买。它基本上会执行一些合乎逻辑的步骤来查看他们是否可以购买该物品。最重要的是,他们在任何给定时间每个用户都限制为 100 个项目。如果所有条件都通过,则使用一个简单的循环来创建他们请求的项目数。
所以,他们正在做的是,通过代理记录 1 个有效的购买请求。然后以高并发重播它,这实际上是每次都允许一些额外的东西通过。因此,如果他们将其设置为购买 100 个数量,他们可以将其增加到 300-400 个,或者如果他们购买 15 个数量,他们可以将其增加到 120 个。
上面的购买方式被包裹在一个交易中。但是,即使它被包装了,它也不会在请求几乎同时执行的某些情况下停止它。我猜这可能需要一些数据库锁定。另一个需要知道的转折是,在任何给定时间,rake 任务都在针对用户表的 cron 作业中运行,以更新玩家的健康和能量属性。所以,这也不能被阻止。
任何帮助都会非常棒。这是我的小爱好项目,我想确保游戏对每个人来说都是公平和有趣的。
非常感谢!
控制器动作:
def hire
worker_asset_type_id = (params[:worker_asset_type_id])
quantity = (params[:quantity])
trade = Trade.new()
trade_response = trade.buy_worker_asset(current_user, worker_asset_type_id, quantity)
user = User.find(current_user.id, select: 'money')
respond_to do |format|
format.json {
render json: {
trade: trade,
user: user,
messages: {
messages: [trade_response.to_s]
}
}
}
end
end
贸易模型法:
def buy_worker_asset(user, worker_asset_type_id, quantity)
ActiveRecord::Base.transaction do
if worker_asset_type_id.nil?
raise ArgumentError.new("You did not specify the type of worker asset.")
end
if quantity.nil?
raise ArgumentError.new("You did not specify the amount of worker assets you want to buy.")
end
if quantity <= 0
raise ArgumentError.new("Please enter a quantity above 0.")
end
quantity = quantity.to_i
worker_asset_type = WorkerAssetType.where(id: worker_asset_type_id).first
if worker_asset_type.nil?
raise ArgumentError.new("There is no worker asset of that type.")
end
trade_cost = worker_asset_type.min_cost * quantity
if (user.money < trade_cost)
raise ArgumentError.new("You don't have enough money to make that purchase.")
end
# Get the users first geo asset, this will eventually have to be dynamic
potential_total = WorkerAsset.where(user_id: user.id).length + quantity
# Catch all for most people
if potential_total > 100
raise ArgumentError.new("You cannot have more than 100 dealers at the current time.")
end
quantity.times do
new_worker_asset = WorkerAsset.new()
new_worker_asset.worker_asset_type_id = worker_asset_type_id
new_worker_asset.geo_asset_id = user.geo_assets.first.id
new_worker_asset.user_id = user.id
new_worker_asset.clocked_in = DateTime.now
new_worker_asset.save!
end
self.buyer_id = user.id
self.money = trade_cost
self.worker_asset_type_id = worker_asset_type_id
self.trade_type_id = TradeType.where(name: "market").first.id
self.quantity = quantity
# save trade
self.save!
# is this safe?
user.money = user.money - trade_cost
user.save!
end
end
【问题讨论】:
-
如何将数据完整性逻辑作为一组 CHECK 约束和触发器推送到数据库中?是的,您永远不应该将逻辑放入数据库中,因为这是 Rails,但这是一种荒谬和无知的态度。
-
@muistooshort 感谢您的建议,作为最后的手段,我愿意做这样的事情。如果有好的解决方案,我想先尝试使用 Rails 方式。
标签: ruby-on-rails ruby postgresql activerecord transactions