You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django Channels在线井字棋用户胜负分存储后被重置问题求助

问题排查与解决方案

核心问题原因

你遇到的分数被重置问题根源是Django模型实例的旧值覆盖:

  • 你在consumers.py中使用的self.scope['user']是WebSocket连接建立时加载的用户实例,后续数据库中用户的胜场数据更新后,这个缓存的实例里的won_games/lost_games/draw_games字段还是连接建立时的初始值(比如0)
  • 当你后续调用缓存实例的lose()/win()方法时,修改某一个字段后执行全字段保存,会把其他字段的旧值直接覆盖数据库中的最新值,就会出现赢了的场次被重置为0的情况。
  • 同理通过presence_set关联查询到的competitor.user也是ORM缓存的旧实例,也会出现同样的覆盖问题。

修复方案

1. 优化模型方法,使用原子更新避免旧值覆盖

用Django的F表达式直接在数据库层面执行数值累加,不需要把字段值读到Python内存中,同时指定update_fields仅更新需要修改的字段,避免其他字段被旧值覆盖:

from django.db.models import F

class CustomUser(AbstractUser):
    won_games = models.IntegerField(default=0)
    lost_games = models.IntegerField(default=0)
    draw_games = models.IntegerField(default=0)

    def win(self):
        self.won_games = F('won_games') + 1
        self.save(update_fields=['won_games']) 

    def lose(self):
        self.lost_games = F('lost_games') + 1
        self.save(update_fields=['lost_games']) 

    def draw(self):
        self.draw_games = F('draw_games') + 1
        self.save(update_fields=['draw_games']) 

2. 优化Consumer逻辑,每次更新前拉取最新的用户实例

不要直接用缓存的用户实例操作,每次统计胜负前从数据库查询最新的用户数据:

class GameConsumer(WebsocketConsumer):
    # some code
    def receive(self, text_data):
        if text_data['type'] == 'completed':
            self_channel = Presence.objects.get(channel_name=self.channel_name)
            competitor_presence = self_channel.room.presence_set.filter(
                                    ~Q(channel_name=self_channel)
                                   ).first()
            if not competitor_presence:
                return
            # 重新查询最新的用户实例,不要用缓存的关联对象
            competitor = CustomUser.objects.get(pk=competitor_presence.user.pk)
            user = CustomUser.objects.get(pk=self.scope['user'].pk)
            competitor.win()
            if user.is_authenticated:
                user.lose()
    return

可选优化:高并发场景加行锁

如果同时有多个游戏局更新同一个用户的分数,可以加select_for_update避免竞态条件:

from django.db import transaction

with transaction.atomic():
    competitor = CustomUser.objects.select_for_update().get(pk=competitor_presence.user.pk)
    user = CustomUser.objects.select_for_update().get(pk=self.scope['user'].pk)
    competitor.win()
    user.lose()

内容的提问来源于stack exchange,提问作者Ahmed Yasser

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.06 18:39:01