¿Es segura la forma en que el inicio rápido de la base de datos de Firebase maneja los recuentos?

Resuelto Zigmārs Dzērve asked hace 54 años • 1 respuestas

Quiero crear un campo de incremento para los Me gusta de artículos.

Me refiero a este enlace: https://firebase.google.com/docs/database/android/save-data#save_data_as_transactions

En el ejemplo hay código para el campo de incremento:

if (p.stars.containsKey(getUid())) {
    // Unstar the post and remove self from stars
    p.starCount = p.starCount - 1;
    p.stars.remove(getUid());
} else {
    // Star the post and add self to stars
    p.starCount = p.starCount + 1;
    p.stars.put(getUid(), true);
}

Pero, ¿cómo puedo estar seguro de si al usuario ya le gustó o no le gustó el artículo?

En el ejemplo, el usuario (hacker) también podría borrar el mapa de estrellas completo como este y se guardará de todos modos:

p.stars = new HashMap<>();

y arruinará la lógica para otros usuarios a los que ya les gustó.

Ni siquiera creo que se puedan establecer reglas para esto, especialmente para la acción de "disminuir el recuento".

¿Alguna ayuda, sugerencia?

Zigmārs Dzērve avatar Jan 01 '70 08:01 Zigmārs Dzērve
Aceptado

Las reglas de seguridad pueden hacer algunas cosas:

  • Asegúrese de que un usuario solo pueda agregar o eliminar el suyo propio uiden el starsnodo.

    "stars": {
      "$uid": {
        ".write": "$uid == auth.uid"
      }
    }
    
  • asegúrese de que un usuario solo pueda cambiar el starCountcuando agrega el suyo propio uidal starsnodo o lo elimina de allí

  • asegúrese de que el usuario solo pueda aumentar/disminuir starCounten 1

Incluso con estos, podría resultar complicado tener una regla de seguridad que garantice que sea starCountigual a la cantidad de uids en el starsnodo. Te animo a que lo pruebes y compartas tu resultado.

Sin embargo, la forma en que he visto a la mayoría de los desarrolladores lidiar con esto es:

  • Comience a contar con el cliente (si el tamaño del starsnodo no es demasiado grande, esto es razonable).
  • tener un proceso confiable ejecutándose en un servidor que agregue los starsarchivos en starCount. Podría usar eventos child_added/child_removed para incrementar/disminuir.

Actualización: con ejemplo práctico

Escribí un ejemplo práctico de un sistema de votación. La estructura de datos es:

votes: {
  uid1: true,
  uid2: true,
},
voteCount: 2

Cuando un usuario vota, la aplicación envía una actualización a varias ubicaciones:

{
  "/votes/uid3": true,
  "voteCount": 3
}

Y luego para eliminar su voto:

{
  "/votes/uid3": null,
  "voteCount": 2
}

Esto significa que la aplicación necesita leer explícitamente el valor actual de voteCount, con:

function vote(auth) {
  ref.child('voteCount').once('value', function(voteCount) {
    var updates = {};
    updates['votes/'+auth.uid] = true;
    updates.voteCount = voteCount.val() + 1;
    ref.update(updates);
  });  
}

Es esencialmente una transacción de múltiples ubicaciones, pero luego incorpora el código de la aplicación y las reglas de seguridad en lugar del SDK de Firebase y el servidor en sí.

Las reglas de seguridad hacen algunas cosas:

  1. asegúrese de que el recuento de votos solo pueda subir o bajar en 1
  2. Asegúrese de que un usuario solo pueda agregar/eliminar su propio voto.
  3. Garantizar que un aumento del recuento vaya acompañado de una votación.
  4. garantizar que una disminución del recuento vaya acompañada de un "voto negativo"
  5. Garantizar que una votación vaya acompañada de un aumento del recuento.

Tenga en cuenta que las reglas no:

  • asegúrese de que un "desvoto" vaya acompañado de una disminución del recuento (se puede hacer con una .writeregla)
  • reintentar votos/anulaciones fallidos (para manejar votaciones/anulaciones concurrentes)

Las normas:

"votes": {
    "$uid": {
      ".write": "auth.uid == $uid",
      ".validate": "(!data.exists() && newData.val() == true &&
                      newData.parent().parent().child('voteCount').val() == data.parent().parent().child('voteCount').val() + 1
                    )"
    }
},
"voteCount": {
    ".validate": "(newData.val() == data.val() + 1 && 
                   newData.parent().child('votes').child(auth.uid).val() == true && 
                   !data.parent().child('votes').child(auth.uid).exists()
                  ) || 
                  (newData.val() == data.val() - 1 && 
                   !newData.parent().child('votes').child(auth.uid).exists() && 
                   data.parent().child('votes').child(auth.uid).val() == true
                  )",
    ".write": "auth != null"
}

jsbin con algún código para probar esto: http://jsbin.com/yaxexe/edit?js,console

Frank van Puffelen avatar Jun 22 '2016 00:06 Frank van Puffelen