【问题标题】:Delete same value from multiple locations Firebase Functions从多个位置删除相同的值 Firebase 函数
【发布时间】:2020-04-15 19:51:23
【问题描述】:

我有一个 firebase 功能,可以在 24 小时后删除旧消息,就像我的旧问题 here 一样。我现在只将 messageIds 存储在用户下的数组中,这样路径是:/User/objectId/myMessages,然后是 myMessages 下所有 messageIds 的数组。 24 小时后,所有消息都会被删除,但用户个人资料下的 iD 会保留在那里。有没有办法继续该功能,以便它也从用户帐户下的数组中删除 messageIds?

我是 Firebase 函数和 javascript 的新手,所以我不知道该怎么做。感谢所有帮助!

【问题讨论】:

    标签: javascript node.js firebase firebase-realtime-database google-cloud-functions


    【解决方案1】:

    更新:不要使用这个答案(我会留下它,因为它可能仍然可以方便地检测删除操作以满足其他需要,但不要用于清理数组的目的在另一个文件中)

    感谢 @samthecodingman 提供原子和并发安全的答案。

    如果使用 Firebase 实时数据库,您可以添加 onChange 事件监听器:

    const functions = require('firebase-functions');
    const admin = require('firebase-admin');
    admin.initializeApp(); 
    
    exports.onDeletedMessage = functions.database.ref('Message/{messageId}').onChange(async event => {
    
        // Exit if this item exists... if so it was not deleted!
        if (event.data.exists()) {
            return;
        }
    
        const userId = event.data.userId; //hopefully you have this in the message document
        const messageId = event.data.messageId;
    
        //once('value') useful for data that only needs to be loaded once and isn't expected to change frequently or require active listening
        const myMessages = await functions.database.ref('/users/' + userId).once('value').snapshot.val().myMessages;
    
        if(!myMessages || !myMessages.length) {
            //nothing to do, myMessages array is undefined or empty
            return;
        }
    
        var index = myMessages.indexOf(messageId);
    
        if (index === -1) {
            //nothing to delete, messageId is not in myMessages
            return;
        }
    
        //removeAt returns the element removed which we do not need
        myMessages.removeAt(index);
        const vals = {
            'myMessages': myMessages;
        }
    
        await admin.database.ref('/users/' + userId).update(vals);
    
    });
    

    如果使用 Cloud Firestore 可以在被删除的文档上添加事件侦听器以处理用户文档中的清理:

    exports.onDeletedMessage = functions.firestore.document('Message/{messageId}').onDelete(async event => {
      const data = event.data();
    
      if (!data) {
        return;
      }
    
      const userId = data.userId; //hopefully you have this in the message document
      const messageId = data.messageId;
    
      //now you can do clean up for the /user/{userId} document like removing the messageId from myMessages property
      const userSnapShot = await admin.firestore().collection('users').doc(userId).get().data();
    
      if(!userSnapShot.myMessages || !userSnapShot.myMessages.length) {
          //nothing to do, myMessages array is undefined or empty
          return;
      }
    
      var index = userSnapShot.myMessages.indexOf(messageId);
    
      if (index === -1) {
          //nothing to delete, messageId is not in myMessages
          return;
      }
    
      //removeAt returns the element removed which we do not need
      userSnapShot.myMessages.removeAt(index);
      const vals = {
            'myMessages': userSnapShot.myMessages;
      }
    
      //To update some fields of a document without overwriting the entire document, use the update() method
      await admin.firestore().collection('users').doc(userId).update(vals);
    
    });
    

    【讨论】:

    • 感谢您的帮助!您能否提供一个基本功能以将其从用户中删除?我对 Javascript 很陌生,但我想它类似于其他函数的运行方式?我只是不知道这一切是如何连接的,所以如果您可以添加一个基本功能来删除该属性,那将非常有帮助!
    • @Jaqueline 确定我添加了一些代码,可以帮助您从 users/{userId} 文档的 myMessages 区域中删除 messageId
    • 我使用的是实时数据库而不是 Firestore。我是否只需将“firestore”替换为“database”
    • 不,我认为我的回答不适用于实时数据库。我相信实时数据库没有删除事件监听器
    • @samthecodingman 谢谢你们俩真正深入了解这个!非常感谢!
    【解决方案2】:

    在旧问题上的@frank-van-puffelen's accepted answer 的基础上,现在这将作为同一原子删除操作的一部分从发件人的用户数据中删除消息 ID,而不会为每条删除的消息触发云函数。

    方法一:并发重构

    在能够使用此方法之前,您必须重组您在 /User/someUserId/myMessages 中存储条目的方式,以遵循 best practices 并发数组到以下内容:

    {
      "/User/someUserId/myMessages": {
        "-Lfq460_5tm6x7dchhOn": true,
        "-Lfq483gGzmpB_Jt6Wg5": true,
        ...
      }
    }
    

    这允许您将之前的函数修改为:

    // Cut off time. Child nodes older than this will be deleted.
    const CUT_OFF_TIME = 24 * 60 * 60 * 1000; // 2 Hours in milliseconds.
    
    exports.deleteOldMessages = functions.database.ref('/Message/{chatRoomId}').onWrite(async (change) => {
        const rootRef = admin.database().ref(); // needed top level reference for multi-path update
        const now = Date.now();
        const cutoff = (now - CUT_OFF_TIME) / 1000; // convert to seconds
        const oldItemsQuery = ref.orderByChild('seconds').endAt(cutoff);
        const snapshot = await oldItemsQuery.once('value');
        // create a map with all children that need to be removed
        const updates = {};
        snapshot.forEach(messageSnapshot => {
            let senderId = messageSnapshot.child('senderId').val();
            updates['Message/' + messageSnapshot.key] = null; // to delete message
            updates['User/' + senderId + '/myMessages/' + messageSnapshot.key] = null; // to delete entry in user data
        });
        // execute all updates in one go and return the result to end the function
        return rootRef.update(updates);
    });
    

    方法二:使用数组

    警告:此方法会成为并发问题的牺牲品。如果用户要在删除操作期间发布新消息,则可以在评估删除时删除它的 ID。尽可能使用方法 1 来避免这种情况。

    此方法假定您的 /User/someUserId/myMessages 对象如下所示(一个普通数组):

    {
      "/User/someUserId/myMessages": {
        "0": "-Lfq460_5tm6x7dchhOn",
        "1": "-Lfq483gGzmpB_Jt6Wg5",
        ...
      }
    }
    

    我能为这个数据结构想出的最精简、最具成本效益的防碰撞功能如下:

    // Cut off time. Child nodes older than this will be deleted.
    const CUT_OFF_TIME = 24 * 60 * 60 * 1000; // 2 Hours in milliseconds.
    
    exports.deleteOldMessages = functions.database.ref('/Message/{chatRoomId}').onWrite(async (change) => {
        const rootRef = admin.database().ref(); // needed top level reference for multi-path update
        const now = Date.now();
        const cutoff = (now - CUT_OFF_TIME) / 1000; // convert to seconds
        const oldItemsQuery = ref.orderByChild('seconds').endAt(cutoff);
        const snapshot = await oldItemsQuery.once('value');
        // create a map with all children that need to be removed
        const updates = {};
        const messagesByUser = {};
        snapshot.forEach(messageSnapshot => {
            updates['Message/' + messageSnapshot.key] = null; // to delete message
    
            // cache message IDs by user for next step
            let senderId = messageSnapshot.child('senderId').val();
            if (!messagesByUser[senderId]) { messagesByUser[senderId] = []; }
            messagesByUser[senderId].push(messageSnapshot.key);
        });
    
        // Get each user's list of message IDs and remove those that were deleted.
        let pendingOperations = [];
        for (let [senderId, messageIdsToRemove] of Object.entries(messagesByUser)) {
            pendingOperations.push(admin.database.ref('User/' + senderId + '/myMessages').once('value')
                .then((messageArraySnapshot) => {
                    let messageIds = messageArraySnapshot.val();
                    messageIds.filter((id) => !messageIdsToRemove.includes(id));
                    updates['User/' + senderId + '/myMessages'] = messageIds; // to update array with non-deleted values
                }));
        }
        // wait for each user's new /myMessages value to be added to the pending updates
        await Promise.all(pendingOperations);
    
        // execute all updates in one go and return the result to end the function
        return ref.update(updates);
    });
    

    【讨论】:

    • 已更新处理传统数组的答案。
    • 非常感谢您的帮助!只有一件事;我收到“解析错误:意外的令牌 oldItemsQuery eslint [19,24],这是针对 const snapshot = await oldItemsQuery.once('value') 的。你知道这意味着什么或如何解决它吗?
    • 可能是由于不正确的 eslint 配置无法正确识别 await 语法。尝试使用{ "parserOptions": { "ecmaVersion": 8 } }
    • 对不起,我在哪里输入?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-29
    • 2017-10-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多