如何限制可对 Cloud Firestore 中的特定集合执行的写入量?
How can I limit the amount of writes that can be done to a certain collection in Cloud Firestore?
我正在寻找一种方法来防止在给定时间段内向(子)集合写入超过给定限制的文档。
例如:Messenger A 每 24 小时不能写超过 1000 条消息。
这应该在 Firebase 函数 API 端点的上下文中完成,因为它由第三方调用。
端点
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
}
}
此代码有效,但有一个很大的 BUT。偏移量确实会跳过 1000 个结果,但 Firebase 仍会向您收费!因此,每次 Messenger 尝试添加 1 条消息时,都会读取 1000 多条消息……这很昂贵。
所以我需要更好(更便宜)的方法来做到这一点。
我想到但尚未尝试的一件事是向消息添加一个 index/counter 字段,每条消息增加 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 的新消息。但是两个请求可以读取并写入相同的索引吗?或者这可以通过交易来处理吗?
或者是否有完全不同的方法来解决我的问题?
我强烈反对在我的服务器端代码中使用 offset()
,正是因为它看起来像是在跳过文档,实际上是在读取并丢弃它们。
我能想到的实现每日最大写入次数的最简单方法是为每个 Messenger 保留一个每日写入计数器,然后在他们写消息时更新该计数器。
例如,您可以在每次写消息时执行以下操作:
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 })
因此,这会在您的 Messenger 文档中为每一天添加一个额外的字段,然后它会记录 Messenger 当天所写消息的数量。
然后您将使用此计数代替当前的 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
}
剩下的步骤:
- 您需要保护对计数器字段的写入权限,因为信使不能自行修改这些字段。
- 如果您担心 Messenger 的文档变得太大,您可能需要定期清除旧消息计数。我更喜欢保留这些类型的计数器,因为它们提供了一个机会,可以在需要时廉价地提供一些统计数据。
我正在寻找一种方法来防止在给定时间段内向(子)集合写入超过给定限制的文档。
例如:Messenger A 每 24 小时不能写超过 1000 条消息。
这应该在 Firebase 函数 API 端点的上下文中完成,因为它由第三方调用。
端点
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
}
}
此代码有效,但有一个很大的 BUT。偏移量确实会跳过 1000 个结果,但 Firebase 仍会向您收费!因此,每次 Messenger 尝试添加 1 条消息时,都会读取 1000 多条消息……这很昂贵。
所以我需要更好(更便宜)的方法来做到这一点。
我想到但尚未尝试的一件事是向消息添加一个 index/counter 字段,每条消息增加 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 的新消息。但是两个请求可以读取并写入相同的索引吗?或者这可以通过交易来处理吗?
或者是否有完全不同的方法来解决我的问题?
我强烈反对在我的服务器端代码中使用 offset()
,正是因为它看起来像是在跳过文档,实际上是在读取并丢弃它们。
我能想到的实现每日最大写入次数的最简单方法是为每个 Messenger 保留一个每日写入计数器,然后在他们写消息时更新该计数器。
例如,您可以在每次写消息时执行以下操作:
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 })
因此,这会在您的 Messenger 文档中为每一天添加一个额外的字段,然后它会记录 Messenger 当天所写消息的数量。
然后您将使用此计数代替当前的 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
}
剩下的步骤:
- 您需要保护对计数器字段的写入权限,因为信使不能自行修改这些字段。
- 如果您担心 Messenger 的文档变得太大,您可能需要定期清除旧消息计数。我更喜欢保留这些类型的计数器,因为它们提供了一个机会,可以在需要时廉价地提供一些统计数据。