如何在 Firebase 实时数据库中创建安全的 Like 计数器

mar*_*rop 4 java android firebase firebase-realtime-database

我的数据库中有一个用户节点,用于存储有关用户的任何信息。然后在我的应用程序中我必须对按钮进行图像处理:

mlike = root.findViewById(R.id.btn_like);
mdislike = root.findViewById(R.id.btn_dislike);
mlike.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
           likeStations();
        }
    });

 mdislike.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
           dislikeStations();
        }
    });

private void dislikeStations(){

 db = FirebaseDatabase.getInstance();
    ref = db.getReference().child("Users").child(user);

    DatabaseReference likesRef = FirebaseDatabase.getInstance().getReference().child("STATIONS").child(station);
    likesRef.addListenerForSingleValueEvent(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {

            if (dataSnapshot.exists()) { 
Boolean dislike_snapshot = dataSnapshot.child("downvote").getValue(Boolean.class);
likeref.child("downvote").setValue(+1)

            }
}
Run Code Online (Sandbox Code Playgroud)

对于 likeStations() 方法来说,情况几乎是一样的。我想知道的是如何确保他们只能单击按钮一次,或者如果他们在单击不喜欢后单击“喜欢”,则会在“喜欢”上添加 1 之前从“不喜欢”中删除 1。

我知道它将使用用户节点,因此我将其添加到问题中。

Fra*_*len 6

它总是从拥有正确的数据结构开始。如果您希望用户只被允许执行一次操作,您需要:

  1. 为了能够通过使用 Firebase 身份验证登录用户来识别用户。
  2. 一种数据结构,允许检查他们之前是否采取过该操作。这通常是一个以 UID 作为键的映射,因为这是确保唯一性的唯一方法。

由于您想分别跟踪赞成票和反对票,那就是:

upvotes: {
  "uid1": true,
  "uid2": true
},
downvotes: {
  "uid3": true
}
Run Code Online (Sandbox Code Playgroud)

现在,您的其余要求是该结构上的一堆安全规则。


例如,允许用户为自己投票,如下所示:

{
  "rules": {
    ...
    "upvotes": {
      "$uid": {
        ".write": "auth.uid === $uid"
      }
    },
    "downvotes": {
      "$uid": {
        ".write": "auth.uid === $uid"
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

接下来,用户只能投赞成票反对票,而不能同时投赞成票或反对票,如下所示:

"upvotes": {
  "$uid": {
    ".write": "auth.uid === $uid
            && !newData.parent().parent().child('downvotes').child(auth.uid).exists()"
  }
},
Run Code Online (Sandbox Code Playgroud)

当然,还有同样的规则downvotes


根据上述规则,用户可以使用此 JSON 补丁(您可以在 Android 中将其构建为 a)投赞成票:Map

{
  "upvotes/uid1": true
}
Run Code Online (Sandbox Code Playgroud)

否决票是:

{
  "downvotes/uid1": true
}
Run Code Online (Sandbox Code Playgroud)

但如果他们已经投了赞成票,那么就会被拒绝,因此您可以通过执行此(单个)写入操作将他们的赞成票切换为反对票:

{
  "upvotes/uid1": null,
  "downvotes/uid1": true
}
Run Code Online (Sandbox Code Playgroud)

这里null删除他们的赞成票,并true写入反对票,两者都是作为单个操作发生的。


储存和保护计数器

最后,这让我们谈到了您所询问的计数,这是与唯一性不同的主题。现在存储计数是可选的,因为该信息已经存在于 UID 的数量中。但这是一个很好的优化,因为如果没有存储的计数器,用户将必须下载所有 UID 值来对其进行计数。

那么数据结构就变成了:

upvotes: {
  "uid1": true,
  "uid2": true
},
upvoteCount: 2,
downvotes: {
  "uid3": true
},
downvoteCount: 1
Run Code Online (Sandbox Code Playgroud)

现在您已经有了数据结构并且我们已经看到了转换,接下来就需要确保计数的变化与投票结果相匹配。

第一个例子是我们上面看到的赞成票片段:您可以在投赞成票时增加赞成票数。规则中

"upvoteCount": {
  ".write": "(
              newData.val() = data.val() + 1
              newData.parent().parent().child('upvotes').child(auth.uid).exists()
             && !data.parent().parent().child('upvotes').child(auth.uid).exists()
            )"
}
Run Code Online (Sandbox Code Playgroud)

如果您阅读上面的定义,我希望这个表达式是有意义的:

  1. 用户只能将点赞数增加1,
  2. 并且只有当他们将其 UID 添加到upvotes列表中时,
  3. 如果它以前不存在

如果他们删除了投票,您同样需要处理减少赞成票数的问题,然后还要处理反对票数的问题。这些规则都很相似,但又是分开的。

一旦完成,最后一步是增强规则upvotesdownvotes要求用户在投票时更新计数。当用户投票时,他们还必须更新计数。这与我们刚刚看到的规则相反:

"upvotes": {
  "$uid": {
    ".write": "auth.uid === $uid
            && newData.exists() 
            && !data.exists()
            &&  newData.parent().parent().child('upvoteCount').val() == 
                   data.parent().parent().child('upvoteCount').val() + 1
  }
},
Run Code Online (Sandbox Code Playgroud)