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

如何配置Firebase实时数据库规则以读取所有用户的mention节点?

核心问题:Firebase Realtime Database权限拒绝,无法读取所有用户的mention节点

我正在配置Firebase Realtime Database规则,想要允许所有已认证用户读取任意其他用户数据里的mention节点,但调用checkMention()方法时一直弹出"permission denied"的Toast提示,不确定当前规则配置是否正确。

当前数据库规则

{
    "rules": {
        "users": {
            "$uid": {
                ".read": "auth.uid == $uid",
                ".write": "auth.uid == $uid"
            },
            "mention": {
                ".read": "true",
                ".write": "auth.uid == null"
            }
        }
    }
}

当前代码实现

checkMention方法

public void checkMention(String mention, MentionListener mentionListener) {
    database.child("users").addListenerForSingleValueEvent(new ValueEventListener() {
        @Override
        public void onDataChange(@NonNull DataSnapshot snapshot) {
            for (DataSnapshot userSnapshot : snapshot.getChildren()) {
                String userMention = userSnapshot.child("mention").getValue(String.class);
                if (userMention != null && userMention.equals(mention)) {
                    if (mentionListener != null) {
                        mentionListener.onMentionData(true);
                    }
                }
            }
        }

        @Override
        public void onCancelled(@NonNull DatabaseError error) {
            Toast.makeText(context, "Error: " + error.getMessage(), Toast.LENGTH_SHORT).show();
            if (mentionListener != null) {
                mentionListener.onMentionData(false);
            }
        }
    });
}

MainActivity对话框代码

private void openDialogUpdate() {
    MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
    DialogUpdateBinding updateBinding = DialogUpdateBinding.inflate(getLayoutInflater());
    AlertDialog dialog = builder.setView(updateBinding.getRoot()).create();

    TextInputLayout usernameInput = updateBinding.usernameInput;
    TextInputEditText inputUsername = updateBinding.inputUsername;
    MaterialButton buttonUpdate = updateBinding.buttonUpdate;

    inputUsername.addTextChangedListener(new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) { }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) { }

        @Override
        public void afterTextChanged(Editable s) {
            if (mentionCheckRunnable != null) {
                handler.removeCallbacks(mentionCheckRunnable);
            }
            mentionCheckRunnable = () -> {
                String mention = "@" + s.toString().trim().replace(" ", "_").toLowerCase();
                if (!mention.equals("@")) {
                    accountFirebase.checkMention(mention, exists -> {
                        if (exists) {
                            usernameInput.setError(getString(R.string.unavailable_mention));
                        } else {
                            usernameInput.setHelperText(getString(R.string.available_mention));
                            usernameInput.setError(null);
                        }
                    });
                }
                buttonUpdate.setText(mention);
            };
            handler.postDelayed(mentionCheckRunnable, 1500);
        }
    });
    
    buttonUpdate.setOnClickListener(v -> {
        if (inputUsername.getText() != null) {
            String uid = accountFirebase.getUid();
            String username = inputUsername.getText().toString().trim();
            if (!username.isEmpty() && username.length() > 8) {
                accountFirebase.updateUsername(username, uid).addOnCompleteListener(task -> {
                    if (task.isSuccessful()) {
                        dialog.dismiss();
                    }
                });
            } else if (!username.isEmpty() && username.length() < 8) {
                showMessage(updateBinding.getRoot(), getString(R.string.username_too_short));
            } else {
                showMessage(updateBinding.getRoot(), getString(R.string.username_empty));
            }
        }
    });

    dialog.setCancelable(false);
    dialog.show();
}

预期目标

  • 用户创建账户进入MainActivity后,弹出对话框要求更新名称
  • 用户输入时,checkMention()检索所有用户的mention节点,判断该mention是否已被占用
  • 仅当mention未被占用时,用户才能完成名称更新

解决方案

1. 修正数据库规则

当前规则的结构错误:你把mention节点直接挂在users下,但实际数据里mention是每个$uid节点的子节点。同时代码读取的是整个users节点,而规则里未开放users节点的读取权限,因此触发权限拒绝。

调整规则,让每个用户节点下的mention子节点对所有已认证用户开放读取权限,同时保留用户其他数据的私有性:

{
    "rules": {
        "users": {
            "$uid": {
                ".read": "auth.uid == $uid",
                ".write": "auth.uid == $uid",
                "mention": {
                    ".read": "auth != null"
                }
            }
        }
    }
}

2. 优化查询代码(推荐)

当前代码读取整个users节点效率极低,随着用户量增加会消耗大量带宽和资源。建议利用Firebase的查询功能直接检索目标mention:

public void checkMention(String mention, MentionListener mentionListener) {
    database.child("users")
            .orderByChild("mention")
            .equalTo(mention)
            .addListenerForSingleValueEvent(new ValueEventListener() {
                @Override
                public void onDataChange(@NonNull DataSnapshot snapshot) {
                    boolean exists = snapshot.exists();
                    if (mentionListener != null) {
                        mentionListener.onMentionData(exists);
                    }
                }

                @Override
                public void onCancelled(@NonNull DatabaseError error) {
                    Toast.makeText(context, "Error: " + error.getMessage(), Toast.LENGTH_SHORT).show();
                    if (mentionListener != null) {
                        mentionListener.onMentionData(false);
                    }
                }
            });
}

这样既减少了数据传输量,又提升了查询效率,配合修正后的规则即可正常读取mention节点数据。

内容的提问来源于stack exchange,提问作者Vanderclin Rocha

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 15:57:06