【问题标题】:Good way to replace invalid characters in firebase keys?替换firebase键中无效字符的好方法?
【发布时间】:2015-08-09 12:16:37
【问题描述】:

我的用例是保存用户信息。当我尝试使用用户的电子邮件地址作为密钥将数据保存到 Firebase 时,Firebase 会引发以下错误:

错误:无效的密钥 e@e.ee(不能包含 .$[]#

因此,显然,我无法通过他们的电子邮件索引用户信息。替换 . 的最佳做法是什么?

我已经成功地将 . 更改为 - 但这不会减少它,因为某些电子邮件的地址中有 -s。

目前,我正在使用

var cleanEmail = email.replace('.','`');

但可能会与此发生冲突。

【问题讨论】:

    标签: firebase


    【解决方案1】:

    在电子邮件地址中,将点 . 替换为逗号 ,。这种模式是最佳实践。

    逗号, 不是电子邮件地址中允许的字符,但它 Firebase 密钥中允许的字符。对称地,点 . 电子邮件地址中允许的字符,但在 Firebase 密钥中不允许。所以直接替换将解决您的问题。您可以索引电子邮件地址而无需循环。

    您还有另一个问题。

    const cleanEmail = email.replace('.',','); // only replaces first dot
    

    只会替换第一个点. 但电子邮件地址可以有多个点。要替换所有点,请使用 正则表达式

    const cleanEmail = email.replace(/\./g, ','); // replaces all dots
    

    或者,您也可以使用split() - join() 模式替换所有点。

    const cleanEmail = email.split('.').join(','); // also replaces all dots
    

    【讨论】:

    【解决方案2】:

    我们已经多次处理过这个问题,虽然从表面上看,使用电子邮件作为密钥似乎是一个简单的解决方案,但它会导致许多其他问题:必须清理/解析电子邮件以便它可以实际使用。如果邮箱发生变化怎么办?

    我们发现更改数据存储方式的格式是更好的方法。假设您只需要存储一件事,即用户名。

    john@somecompany.com: "John Smith"
    

    改成

    randomly_generated_node_name
       email:  "john@somecompany.com"
       first:  "John"
       last:   "Smith"
    

    random_generated_node_name 是 Firebase 可以通过 childByAutoId 生成的字符串,或者实际上是任何类型的不直接绑定到数据的引用。

    这提供了很大的灵活性:您现在可以更改人的姓氏 - 比如说他们是否结婚。或更改他们的电子邮件。您可以添加可用于排序的“索引”子 0、1、2 等。可以查询任何子数据的数据。都是因为 random_generated_node_name 是对节点内变量子数据的静态引用。

    它还允许您在将来扩展数据而不更改现有数据。添加地址、喜欢的食物、排序索引等。

    编辑:在 ObjC 中对电子邮件的 Firebase 查询:

    //references all of the users ordered by email
    FQuery *allUsers = [myUsersRef queryOrderedByChild:@"email"];
    
    //ref the user with this email
    FQuery *thisSpecificUser = [allUsers queryEqualToValue:@“john@somecompany.com”]; 
    
    //load the user with this email
    [thisSpecificUser observeEventType:FEventTypeChildAdded withBlock:^(FDataSnapshot *snapshot) {
      //do something with this user
    }];
    

    【讨论】:

    • 谢谢杰。我应该指定得更好。我已经按照您在主 /users 数据库中的建议保存了数据。作为键的电子邮件用于索引查找。所以我的 index/users/email/'some@email.com' 设置为实际的用户 ID。也许我会更多地考虑如何通过类似数组的格式进行索引
    • 是的 - 这只会导致各种编码问题。最好不要使用实际的电子邮件(或任何可能更改的数据)作为密钥。您可能只想为每个用户节点添加一个索引子节点:每个用户中的索引:“0”,索引:“1”等。这样,您无需实际使用数组即可获得类似数组的行为(这通常是一个坏主意),并且您可以通过他们的姓名、电子邮件或索引查询特定用户。
    • 啊,也许我还没有看到它,但是在找到我正在查找的电子邮件之前,我不需要遍历所有用户吗?我使用电子邮件作为索引中的键进行直接查找并了解属性更改时所需的额外代码。我糊涂了吗?
    • 嗯,您可以循环访问它们,但更简单的解决方案是查询电子邮件地址。我在答案中添加了一个 ObjC 查询示例。
    • 欣赏周到的周杰伦,但这实际上是我试图通过直接查找索引来避免的。这样就无需等待任何事情,而且我们将在没有任何明显问题的情况下进行扩展。
    【解决方案3】:

    我可以想到两种主要的方法来解决这个问题:

    1. 编码/解码功能

    由于 Firebase 密钥中允许的字符集有限,解决方案是将密钥转换为有效格式(编码)。然后有一个反函数(decode)将编码的密钥转换回原始密钥。

    一般的编码/解码函数可能会将原始密钥转换为字节,然后将它们转换为十六进制表示。但是密钥的大小可能是个问题。

    假设您想使用电子邮件作为键来存储用户:

    # path: /users/{email} is User;
    /users/alice@email.com: {
        name: "Alice",
        email: "alice@email.com"
    }
    

    由于路径中的点,上面的示例不起作用。所以我们使用encode函数将key转换成有效的格式。十六进制的alice@email.com616c69636540656d61696c2e636f6d,那么:

    # path: /users/{hex(email)} is User;
    /users/616c69636540656d61696c2e636f6d: {
        name: "Alice",
        email: "alice@email.com"
    }
    

    只要共享相同的hex 函数,任何客户端都可以访问该资源。

    编辑:Base64 也可用于对密钥进行编码/解码。可能比十六进制更有效,但有许多不同的实现。如果客户端不共享完全相同的实现,那么它们将无法正常工作。

    也可以使用专门的功能(例如仅处理电子邮件)。但一定要处理所有的边缘情况。

    1. 存储原始密钥的编码函数

    对密钥进行单向转换要容易得多。因此,不要使用解码函数,只需将原始密钥存储在数据库中即可。

    在这种情况下,一个很好的编码函数是 SHA-256 算法。这是一种在许多平台上都有实现的通用算法。而且发生碰撞的可能性很小。

    前面使用 SHA-256 的示例变成这样:

    # path: /users/{sha256(email)} is User;
    /users/55bf4952e2308638427d0c28891b31b8cd3a88d1610b81f0a605da25fd9c351a: {
        name: "Alice",
        email: "alice@email.com"
    }
    

    任何具有原始密钥(电子邮件)的客户端都可以找到此条目,因为编码功能是已知的(它是已知的)。而且,即使密钥变大,SHA-256 的大小也始终相同,因此可以保证是有效的 Firebase 密钥。

    【讨论】:

    • 这可能是针对 OP 中提出的特殊用例(即电子邮件点)的通用解决方案。我很好奇 Firebase 团队是否认可这种模式为“最佳实践”? @FrankvanPuffelen
    • @Mowzer 我现在在其他地方使用同样的想法。我将重写答案以使其更笼统。很想听听 Firebase 团队的潜在问题。但到目前为止,这对我来说效果很好。
    【解决方案4】:

    我正在使用以下代码将电子邮件转换为哈希,然后将哈希用作 firebase 中的键

    public class HashingUtils {
        public HashingUtils() {
        }
    
        //generate 256 bits hash using SHA-256
        public String generateHashkeySHA_256(String email){
            String result = null;
            try {
                MessageDigest digest = MessageDigest.getInstance("SHA-256");
                byte[] hash = digest.digest(email.getBytes("UTF-8"));
                return byteToHex(hash); // make it printable
            }catch(Exception ex) {
                ex.printStackTrace();
            }
            return result;
        }
    
        //generate 160bits hash using SHA-1
        public String generateHashkeySHA_1(String email){
            String result = null;
            try {
                MessageDigest digest = MessageDigest.getInstance("SHA-1");
                byte[] hash = digest.digest(email.getBytes("UTF-8"));
                return byteToHex(hash); // make it printable
            }catch(Exception ex) {
                ex.printStackTrace();
            }
            return result;
        }
    
        public String byteToHex(byte[] bytes) {
            Formatter formatter = new Formatter();
            for (byte b : bytes) {
                formatter.format("%02x", b);
            }
            String hex = formatter.toString();
            return hex;
        }
    }
    

    将用户添加到 firebase 的代码

    public void addUser(User user) {
        Log.d(TAG, "addUser: ");
        DatabaseReference userRef= database.getReference("User");
    
        if(!TextUtils.isEmpty(user.getEmailId())){
           String hashEmailId= hashingUtils.generateHashkeySHA_256(user.getEmailId());
            Log.d(TAG, "addUser: hashEmailId"+hashEmailId);
            userRef.child(hashEmailId).setValue(user);
        }
        else {
            Log.d(TAG,"addUser: empty emailId");
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2012-01-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-09-25
      • 1970-01-01
      相关资源
      最近更新 更多