使用angularfire将用户分为n个大小组

Grouping users into n size groups using angularfire

我正在编写一个 Angular/Firebase 应用程序,其中为访问等候室页面的用户分配了一个组,一旦该组有 n 个用户,就会形成一个新组。使用 transactions 似乎是写入路径,但卡住了。

在下面的示例中,我有一个 Config 服务 returns 一个 $firebaseObject 此对象包含组大小或 playerLimit.

angular.module('floodStudyApp')
  .controller('WaitingroomCtrl', function ( $scope, $routeParams, Ref, $location, Config) {
    Config.getConfig($routeParams.floodstudy).$loaded(function (config) {
      $scope.floodstudyConfig = config;
      var NUM_PLAYERS = config.playerLimit;
      Ref.child('floodStudy/'+ config.name+ '/group' + $scope.floodstudyConfig.groups +  '/players').transaction(function(playerList) {
        if (playerList === null) {
          playerList = {};
        }
        if(playerList.hasOwnProperty($routeParams.player)){
          console.log("you already are here dude!");
          return;
        }
        if(Object.keys(playerList).length % NUM_PLAYERS === 0) {
          $scope.floodstudyConfig.groups++;
          $scope.floodstudyConfig.$save();
        }
          playerList[$routeParams.player] = {
            name: $routeParams.player,
            startTime: Firebase.ServerValue.TIMESTAMP,
            playerIndex: Object.keys(playerList).length+1
          };
          return playerList;
        //}
      },  function(error, committed, snapshot){
        if(!error, committed){
          $scope.$apply(function () {
            $location.path('floodstudy/' + $routeParams.floodstudy+ '/group' + $scope.floodstudyConfig.groups + '/' + $routeParams.player);
          });
        }
      });//end transaction
    });// end get config
  });

假设用户激增,我需要每个组恰好有 n 个用户。上面的代码处理了少量用户,而不是激增的用户。当被敲定时,每个组包含 2-6 个用户。将不胜感激关于如何实现这一点的任何建议。

这是浪涌后的示例输出:https://gist.github.com/shawnzam/041f4e26bc98a3f89a7b

考虑到顺序的所有原因,numeric ids fall over in distributed data 与其尝试对数组执行此操作,不如建议您使用计数器,简化并从每个 Zig 中获得很好的公正性。

建议的数据结构:

/rooms/$roomid/counter
/members/$counter_value/$memberid

更新计数器的函数:

angular.factory('updateRoomCounter', function($q, Ref) {
  return function(roomId) {
    return $q(function(resolve, reject) {
      Ref.child('rooms/'+roomId+'/counter').transaction(function(currentValue) {
        if( currentValue >= <NUM_PLAYERS> ) { return; }
        return (currentValue||0)+1;
      }, function(err, committed, snap) {
        if( err || !committed ) { 
          reject(err || 'Too Many Players'); 
        }
        else { 
          resolve(snap.val()); 
        }
      });
    });
  }
});

使用计数器更新:

angular.controller(..., function(updateRoomCounter, Ref) {
  function addAPlayer(roomId, userId) {
    updateRoomCounter(roomId).then(function(myIndex) {
      Ref.child('members/'+roomId+'/'+myIndex).set(userId, <doneFunction>, <errorFunction>);
    }, function() {
      // failed: try the next room?
    })
  }
});

强制执行结构的安全规则:

{
  "rules":  {
    "rooms": {
      "$room_id": {
        "counter": {
          ".write": "newData.exists()",
          ".validate": "newData.isNumber() && (newData.val() == data.val() + 1 || !data.exists() && newData.val() == 1)"
        }
      }
    },
    "members": {
      "$room_id": {
        "$counter_value": {
          ".write": "newData.val() === auth.uid && !data.exists() && newData.exists() && $counter_value <= root.child('rooms/'+$room_id+'/counter').val()"
        }
      }
    }
  }
}

Kato 的回答是实现您的用例的好方法。我想谈谈您为什么会遇到这个问题。

Firebase 事务在混合的客户端和服务器模型上工作。您为客户端上的 transaction() 运行 编写的代码。它获取当前值作为输入和 returns 新值(如果不需要更改,则不获取任何内容)。然后将整个 "current value + new value" 包发送到 Firebase 服务器。然后 Firebase 服务器进行比较和设置。如果存储的值与您开始交易时使用的值相同,则将使用您的新值。如果当前值在此期间发生变化,您的新值将被拒绝,您的交易处理程序将再次 运行。

在您的事务处理程序中,您不只是更新当前值。你还有这个片段:

    if(Object.keys(playerList).length % NUM_PLAYERS === NUM_PLAYERS) {
      $scope.floodstudyConfig.groups++;
      $scope.floodstudyConfig.$save();
    }

由于这修改了 current/return 值之外的数据,因此它不是事务的一部分。因此,即使您 return 来自服务器交易的新值被拒绝,您也已经更新了组。

我相信我有解决办法。我不只计算每个组的玩家,还计算当前组。我在 {currentPlayers: 0, groups: 0}.

形式的单个对象中执行此操作

更新计数器对象的函数:

angular.module('floodStudyApp')
  .factory('updateGroupCounter', function($q, Ref) {
    return function(floodstudy, NUM_PLAYERS) {
      return $q(function(resolve, reject) {
        Ref.child('floodStudyConfig/'+floodstudy+'/currentPlayers').transaction(function(currentValue) {
          if(currentValue.currentPlayers >= NUM_PLAYERS ) { return {currentPlayers: 1, groups: currentValue.groups +1}; }
          return ({currentPlayers: currentValue.currentPlayers+1, groups: currentValue.groups});
        }, function(err, committed, snap) {
          if( err || !committed ) {
            reject(err || 'Too Many Players');
          }
          else {
            resolve(snap.val());
          }
        });
      });
    }
  });

更新计数器对象的函数:

angular.module('floodStudyApp')
  .controller('WaitingroomCtrl', function ( $scope, $routeParams, Ref, $location, Config, updateGroupCounter) {
    function addAPlayer(roomId, userId, NUM_Player) {
      updateGroupCounter(roomId, NUM_Player).then(function(currentConfig) {
        Ref.child('floodstudy/' + roomId + '/group' + currentConfig.groups+ '/players/' + currentConfig.currentPlayers).set({
          user: userId,
          playerIndex: currentConfig.currentPlayers,
        })
      }, function() {
        console.log("full");
      })
    }

    Config.getConfig($routeParams.floodstudy).$loaded(function (config) {
      $scope.floodstudyConfig = config;
      var NUM_PLAYERS = config.playerLimit;
      addAPlayer($routeParams.floodstudy, $routeParams.player, NUM_PLAYERS);
    });// end get config
  });