【问题标题】:Writing rules in Firebase database在 Firebase 数据库中编写规则
【发布时间】:2019-12-02 13:18:50
【问题描述】:

我正在构建一个管理用户和班次的 Android 应用。每个用户可以分配到多个班次,每个班次可以有多个用户。每个班次都与一个日期相关联。

这就是我的数据库在 firebase 中的结构:

 {
  "shift-assign" : {
    "key1" : {
      "111" : true,
      "222" : true,
      "888" : true
    },
    "key2" : {
      "111" : true,
      "222" : true
    }
  },
  "shifts" : {
    "key1" : {
      "date" : "20191202",
      "endTime" : "02:00",
      "numOfEmps" : 4,
      "startTime" : "21:00",
      "wage" : 30
    },
    "key2" : {
      "date" : "20191202",
      "endTime" : "12:00",
      "numOfEmps" : 3,
      "startTime" : "08:00",
      "wage" : 10
    },
    "key3" : {
      "date" : "20191203",
      "endTime" : "06:00",
      "numOfEmps" : 3,
      "startTime" : "00:00",
      "wage" : 30
    }
  },
  "user-assign" : {
    "111" : {
      "key1" : true,
      "key2" : true
    },
    "222" : {
      "key1" : true,
      "key2" : true
    },
    "888" : {
      "key1" : true
    }
  },
  "users" : {
    "111" : {
      "active" : true,
      "email" : "111",
      "firstName" : "aaa",
      "id" : "111",
      "lastName" : "aaa",
      "password" : "111",
      "phone" : "1111111"
    },
    "222" : {
      "active" : true,
      "email" : "222",
      "firstName" : "bbb",
      "id" : "222",
      "lastName" : "bbb",
      "password" : "222",
      "phone" : "4444444"
    },
    "888" : {
      "active" : false,
      "email" : "888",
      "firstName" : "ccc",
      "id" : "888",
      "lastName" : "ccc",
      "password" : "888",
      "phone" : "5555555"
    },
    "99999" : {
      "active" : true,
      "firstName" : "Admin",
      "id" : "99999",
      "password" : "123"
    }
  }
}

当用户尝试将自己分配到班次时,我想在实际分配到班次之前检查一些约束。

我尝试使用事务,但代码搞砸了,因为我需要在事务中引用不同的节点。

我的代码现在看起来像这样:

  private void AssignUserToShift() {
        // Constraint No. 0
        // user cannot be assigned to shifts whose date < today
        if(dates[0].compareTo(LocalDateTime.now().toLocalDate().format(dtf)) < 0){
            Toast.makeText(getApplicationContext(), "Cannot assign to prior dates", Toast.LENGTH_SHORT).show();
            return;
        }
        Map<String, Object> pathsToUpdate = new HashMap<>();
        pathsToUpdate.put("shift-assign/" + shiftKey + "/" + userId, true);
        pathsToUpdate.put("user-assign/" + userId + "/" + shiftKey, true);
        mDatabase.updateChildren(pathsToUpdate, new DatabaseReference.CompletionListener() {
            @Override
            public void onComplete(@Nullable DatabaseError databaseError, @NonNull DatabaseReference databaseReference) {
                if(databaseError == null) {
                    // add to adapter's list in GUI
                    AddUserToList();
                }else{
                    Toast.makeText(getApplicationContext(), "Could not update database entries", Toast.LENGTH_SHORT).show();
                }
            }
        });
    }

和相反的方法:

private void RemoveUserFromShift() {
    mUser user = userList.stream()
            .filter(u -> u.getId().equals(userId))
            .findAny()
            .orElse(null);
    if(user == null){
        Toast.makeText(UsersAssignedActivity.this, "You are not assigned to this shift.", Toast.LENGTH_SHORT).show();
        return;
    }

    AlertDialog.Builder builder = new AlertDialog.Builder(UsersAssignedActivity.this, R.style.AlertDialogCustom);
    builder.setMessage("Do you wish to remove yourself from this shift?");
    builder.setCancelable(false);
    builder.setPositiveButton("Yes",
            new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int i) {
                    Map<String, Object> pathsToDelete = new HashMap<>();
                    pathsToDelete.put("shift-assign/" + shiftKey + "/" + userId, null);
                    pathsToDelete.put("user-assign/" + userId + "/" + shiftKey, null);
                    mDatabase.updateChildren(pathsToDelete, new DatabaseReference.CompletionListener() {
                        @Override
                        public void onComplete(@Nullable DatabaseError databaseError, @NonNull DatabaseReference databaseReference) {
                            if(databaseError == null){
                                // remove from adapter's list in GUI
                                RemoveUserFromList();
                            }else{
                                Toast.makeText(UsersAssignedActivity.this, "Could not delete database entries", Toast.LENGTH_SHORT).show();
                            }
                        }
                    });
                }
            });
    builder.setNegativeButton("No",
            new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int i) {
                    dialog.cancel();
                }
            });
    AlertDialog alertDialog = builder.create();
    alertDialog.show();
}

所以我想我会改用规则并检查服务器端的约束。

我想写满足以下条件的规则——

  1. 用户不能分配到已满的班次。 (其numOfEmps等于shift-assign/shiftKey下的子节点数)

  2. 用户在某个日期不能分配超过一半的班次(如果同一日期有 4 个班次,并且用户已经被分配到其中一个班次,他们将只被允许分配在该日期再分配一个班次。)

    1. 用户不得连续工作超过 4 天。 (如果用户尝试将自己分配到某个日期的班次,并且用户在过去 4 天内已分配到班次,则他们将无法将自己分配到该班次)

    2. 最后,如果用户尚未分配到此班次。我想这可以在客户端使用事务进行检查。

如何编写这些规则?

【问题讨论】:

    标签: java android firebase firebase-realtime-database firebase-security


    【解决方案1】:

    您的问题有一个非常广泛的用例。我将向您展示如何从第一个要求开始:

    用户不能分配到已满的班次。 (其numOfEmps等于shift-assign/shiftKey下的子节点个数)

    安全规则无法对数据执行任何聚合。由于此要求要求您比较两个值,因此这两个值都必须存储在数据库中规则中已知的路径上。

    您已经拥有numOfEmps,但您还需要存储已注册轮班的人数。例如,您可以将注册人数添加到班次中,然后在每次添加/删除时更新它:

    "shifts" : {
      "key1" : {
        "date" : "20191202",
        "endTime" : "02:00",
        "numOfEmps" : 4,
        "numSignups" : 3,
        "startTime" : "21:00",
        "wage" : 30
      },
    

    现在您可以检查安全规则是否有开放的插槽:

    {
      "rules": {
        "shift-assigns": {
          "$shiftid": {
            "$uid": {
              ".validate": "root.child('/shifts/$shiftid/numSignups').val() < root.child('/shifts/$shiftid/numOfEmps').val()"
            }
          }
        }
      }
    }
    

    当然,您还需要确保用户:

    • 只能使用 $uid === auth.uid 之类的方式在班次中添加/删除自己。
    • 只能将自己添加到班次中,但您的数据模型已经确保了这一点。 ?
    • 写入操作还会更新新的numSignups。这在安全规则中是可能的,尽管有点涉及。如需更长的解释,请在此处查看我的答案:Is the way the Firebase database quickstart handles counts secure?

    完成此操作后,您可以一项一项地着手处理其他要求,直到满足所有要求。

    总而言之,对于所有这些用例,您的安全规则将变得相当复杂。虽然您的要求似乎都是可能的,但您需要花费大量时间来学习如何处理安全规则的限制,以及如何在其中实施您的用例。

    另一种方法是在云函数中实现相同的逻辑,然后从应用程序代码中调用该逻辑。 Cloud Functions 对于大多数开发人员来说更自然一些,也可以是一个有效的选择。

    【讨论】:

    • 但是,如果我使用您的建议,我将需要 numSignups 的先前值,以便在每次添加/删除时更改它。
    • 是的,您确实需要当前值和新值。这些分别在datanewData 中可用。
    猜你喜欢
    • 2018-08-27
    • 2020-11-25
    • 2020-12-28
    • 2018-07-22
    • 2021-06-24
    • 2019-10-11
    • 1970-01-01
    • 2017-07-15
    • 2022-10-13
    相关资源
    最近更新 更多