Как я могу ограничить количество операций записи в определенную коллекцию в Cloud Firestore? - PullRequest
0 голосов
/ 09 апреля 2020

Я ищу способ предотвратить запись более (определенного) предела документов в (под) коллекцию в заданном периоде.

Например: Messenger A не разрешено писать более 1000 сообщений в сутки.

Это должно быть сделано в контексте конечной точки API-функции Firebase, поскольку она вызывается третьими сторонами.

Конечная точка

app.post('/message', async function(req:any, res:any) {
        // get the messenger's API key
        const key = req.query.key

        // if no key was provided, return error
        if(!key) {
            res.status(400).send('Please provide a valid key')
            return
        }

        // get the messenger by the provided key
        const getMessengerResult = await admin.firestore().collection('messengers')
            .where('key', '==', key).limit(1).get()

        // if there is no result the messenger is not valid, return error
        if (getMessengerResult.empty){
            res.status(400).send('Please provide a valid key')
            return
        }

        // get the messenger from the result
        const messenger = getMessengerResult.docs[0]

        // TODO: check if messenger hasn't reached limit of 1000 messages per 24 hours

        // get message info from the body
        const title:String = req.body.title
        const body: String = req.body.body

        // add message
        await messenger.ref.collection('messages').add({
            'title':title,
            'body':body,
            'timestamp': Timestamp.now()
        })

        // send response
        res.status(201).send('The notification has been created');
    })

Одна вещь, которую я попробовал, была следующая часть кода вместо TODO::

// get the limit message and validate its timestamp
const limitMessageResult = await messenger.ref.collection('messages')
    .orderBy('timestamp',"desc").limit(1).offset(1000).get()

if(!limitMessageResult.empty) {
    const limitMessage = limitMessageResult.docs[0]
    const timestamp: Timestamp = limitMessage.data()['timestamp']

    // create a date object for 24 hours ago
    const 24HoursAgo = new Date()
    24HoursAgo.setDate(24HoursAgo.getDate()-1)

    if(24HoursAgo < timestamp.toDate()) {
        res.status(405).send('You\'ve exceeded your messages limit, please try again later!')
        return
    }
}

Этот код работает, но есть большое НО. Смещение действительно пропускает 1000 результатов, но Firebase все равно будет платить за это! Поэтому каждый раз, когда мессенджер пытается добавить 1 сообщение, 1000+ читаются ... и это дорого.

Поэтому мне нужен лучший (более дешевый) способ сделать это.

Одна вещь, которую я Придумал, но еще не пытался добавить поле индекса / счетчика к сообщению, которое увеличивается на 1 каждое сообщение. Тогда вместо того, чтобы делать:

const limitMessageResult = await messenger.ref.collection('messages')
    .orderBy('timestamp',"desc").limit(1).offset(1000).get()

Я мог бы сделать что-то вроде:

const limitMessageResult = await messenger.ref.collection('messages')
    .where('index','==', currentIndex-1000).limit(1).get()

Но мне было интересно, будет ли это безопасным способом. Например, что произойдет, если есть несколько запросов одновременно. Сначала мне нужно получить текущий индекс из последнего сообщения и добавить новое сообщение с индексом + 1. Но могут ли два запроса прочитать и, таким образом, записать один и тот же индекс? Или это может быть обработано транзакциями?

Или есть совершенно другой способ решить мою проблему?

1 Ответ

1 голос
/ 11 апреля 2020

Я категорически против использования offset() в моем коде на стороне сервера, именно потому, что он создает впечатление пропуска документов, когда он фактически читает и отбрасывает их.

Самый простой способ Я могу подумать о том, чтобы реализовать максимальное количество записей в день для хранения счетчика записей в день для каждого мессенджера, который вы затем обновляете всякий раз, когда они пишут сообщение.

Например, вы можете делайте следующее всякий раз, когда вы пишете сообщение:

await messenger.ref.collection('messages').add({
    'title':title,
    'body':body,
    'timestamp': Timestamp.now()
})
const today = new Date().toISOString().substring(0, 10); // e.g. "2020-04-11"
await messenger.ref.set({
  [today]: admin.firestore.FieldValue.increment(1)
}, { merge: true })

Таким образом, это добавляет дополнительное поле к вашему документу посланника на каждый день, когда оно ведет подсчет количества сообщений, написанных посланником за этот день. .

Затем вы использовали бы этот счет вместо текущего limitMessageResult.

const messageCount = (await messenger.get()).data()[today] || 0;
if (messageCount < 1000) {
  ... post the message and increase the counter
}
else {
  ... reject the message, and return a message
}

Осталось выполнить следующие шаги:

  • Вы захотите безопасный доступ на запись к полям счетчика, так как мессенджер не может изменять их самостоятельно.
  • Возможно, вы захотите периодически очищать старые сообщения, если вы беспокоитесь о документ посланника становится слишком большим. Я предпочитаю оставлять эти типы счетчиков, так как они дают возможность предоставлять некоторые статистические данные при необходимости.
...