3 回答
TA贡献1852条经验 获得超1个赞
诀窍是对用户上次发布消息的时间进行审核。然后,您可以根据审核值来强制发布每条消息的时间:
{
"rules": {
// this stores the last message I sent so I can throttle them by timestamp
"last_message": {
"$user": {
// timestamp can't be deleted or I could just recreate it to bypass our throttle
".write": "newData.exists() && auth.uid === $user",
// the new value must be at least 5000 milliseconds after the last (no more than one message every five seconds)
// the new value must be before now (it will be since `now` is when it reaches the server unless I try to cheat)
".validate": "newData.isNumber() && newData.val() === now && (!data.exists() || newData.val() > data.val()+5000)"
}
},
"messages": {
"$message_id": {
// message must have a timestamp attribute and a sender attribute
".write": "newData.hasChildren(['timestamp', 'sender', 'message'])",
"sender": {
".validate": "newData.val() === auth.uid"
},
"timestamp": {
// in order to write a message, I must first make an entry in timestamp_index
// additionally, that message must be within 500ms of now, which means I can't
// just re-use the same one over and over, thus, we've effectively required messages
// to be 5 seconds apart
".validate": "newData.val() >= now - 500 && newData.val() === data.parent().parent().parent().child('last_message/'+auth.uid).val()"
},
"message": {
".validate": "newData.isString() && newData.val().length < 500"
},
"$other": {
".validate": false
}
}
}
}
}
在这个小提琴中看到它的实际效果。这是小提琴演奏的要点:
var fb = new Firebase(URL);
var userId; // log in and store user.uid here
// run our create routine
createRecord(data, function (recordId, timestamp) {
console.log('created record ' + recordId + ' at time ' + new Date(timestamp));
});
// updates the last_message/ path and returns the current timestamp
function getTimestamp(next) {
var ref = fb.child('last_message/' + userId);
ref.set(Firebase.ServerValue.TIMESTAMP, function (err) {
if (err) { console.error(err); }
else {
ref.once('value', function (snap) {
next(snap.val());
});
}
});
}
function createRecord(data, next) {
getTimestamp(function (timestamp) {
// add the new timestamp to the record data
var data = {
sender: userId,
timestamp: timestamp,
message: 'hello world'
};
var ref = fb.child('messages').push(data, function (err) {
if (err) { console.error(err); }
else {
next(ref.name(), timestamp);
}
});
})
}
TA贡献1830条经验 获得超9个赞
现有答案使用两个数据库更新:(1)标记时间戳,以及(2)将标记的时间戳附加到实际写入。Kato的答案需要500毫秒的时间窗口,而ChiNhan的答案则需要记住下一个键。
在单个数据库更新中,有一种更简单的方法来执行此操作。这个想法是使用update()方法一次将多个值写入数据库。安全规则将验证写入的值,以使写入不超过配额。配额被定义为一对值:quotaTimestamp和postCount。postCount是在quotaTimestamp的1分钟内写入的帖子数。如果postCount超过某个值,则安全规则仅拒绝下一次写入。当quotaTimestamp超过1分钟时,将重置postCount。
这是发布新消息的方法:
function postMessage(user, message) {
const now = Date.now() + serverTimeOffset;
if (!user.quotaTimestamp || user.quotaTimestamp + 60 * 1000 < now) {
// Resets the quota when 1 minute has elapsed since the quotaTimestamp.
user.quotaTimestamp = database.ServerValue.TIMESTAMP;
user.postCount = 0;
}
user.postCount++;
const values = {};
const messageId = // generate unique id
values[`users/${user.uid}/quotaTimestamp`] = user.quotaTimestamp;
values[`users/${user.uid}/postCount`] = user.postCount;
values[`messages/${messageId}`] = {
sender: ...,
message: ...,
...
};
return this.db.database.ref().update(values);
}
安全规则将速率限制为每分钟最多5个帖子:
{
"rules": {
"users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid && newData.child('postCount').val() <= 5",
"quotaTimestamp": {
// Only allow updating quotaTimestamp if it's staler than 1 minute.
".validate": "
newData.isNumber()
&& (newData.val() === now
? (data.val() + 60 * 1000 < now)
: (data.val() == newData.val()))"
},
"postCount": {
// Only allow postCount to be incremented by 1
// or reset to 1 when the quotaTimestamp is being refreshed.
".validate": "
newData.isNumber()
&& (data.exists()
? (data.val() + 1 === newData.val()
|| (newData.val() === 1
&& newData.parent().child('quotaTimestamp').val() === now))
: (newData.val() === 1))"
},
"$other": { ".validate": false }
}
},
"messages": {
...
}
}
}
注意:应保持serverTimeOffset以避免时钟偏斜。
添加回答
举报