我正在开发一款游戏,用户可以在其中获得经验值。我的(自定义)用户模型看起来像这样:
class TriariadUser(AbstractBaseUser, PermissionsMixin):
pseudonym = models.CharField(max_length=40)
level = models.PositiveSmallIntegerField(default=1)
experience_points = models.PositiveIntegerField(default=0)
def add_ep_points(self, points):
self.experience_points += points
if self.experience_points >= get_next_level_ep(self.level):
level += 1
self.save()
现在我有各种信号监听器可以为用户添加经验值。问题是:如果在一个请求期间发生多个XP增益,则最后一次XP增益会覆盖所有其他增益。
显然这是竞争条件,因此我尝试将我的函数修改为静态并使用select_for_update
:
@staticmethod
def add_ep_points(user_id, points):
user = TriariadUser.objects.select_for_update.get(pk=user_id)
user.experience_points += points
...
这可以按预期工作,但模板中的用户对象不会更新。也就是说,必须要求用户看到发生的事情。使用django调试工具栏,我可以看到,加载用户的请求是在开始时创建的。之后,进行所有相关更新。但之后不会重新加载用户,因此会显示旧状态。
我可以想到各种变通方法,例如使用JavaScript重新加载,但必须有一些其他解决方案(至少我希望如此)。
有没有办法在django中锁定一个对象?有没有办法告诉对象需要重新加载?有没有更好的方法来实现这一点(可能使用某种中间件?)。
答案 0 :(得分:3)
为了避免这就是我要做的事情。首先创建一个与用户关系的UserExpRecord
模型和一个+/-数量,表示您要添加或删除的xp数量。然后这些信号可以添加一个新的UserExpRecord
来为用户提供xp。让UserExpRecord
发出保存信号,通知用户模型需要重新收集(SUM)与用户相关的所有xp记录并将该值保存给用户。
这为您提供了为用户添加xp的时间和数量的记录的直接好处。第二个好处是您可以避免任何种类的竞争条件,因为您没有尝试锁定表行并增加值。
也就是说,根据你的后端,可能有一个原子线程安全的“upsert”或“increment”函数,它允许你在事务中安全地递增一个值,同时阻止所有其他写入。这将允许写入正确堆叠。我相信第一个解决方案(单独的xp记录)会比这个或你当前的解决方案(丢失/丢弃xp更新的未知竞争条件)带来更小的麻烦(缓存失效)。