首页 > 解决方案 > Firebase 实时数据库同时预订一个时隙(覆盖问题)

问题描述

我正在尝试制作一个使用实时数据库预订时间段的应用程序。问题是如果两个用户选择了当前空闲的某个时间段,并且他们都同时点击了书,数据被覆盖了。

我的预订技术是插入一个键 = 时间戳和值 = 用户 ID 的节点。

因此,双方都认为他们为自己保留了插槽,而实际上其中一个预订请求覆盖了数据库节点上的另一个。

我尝试使用Firebase 文档中的Save data as transactions Here。但是,插槽预订仍然会相互覆盖。

这是我的代码:

                dbRef.child(String.valueOf(selectedDate.getTime())).runTransaction(new Transaction.Handler() {
            @Override
            public Transaction.Result doTransaction(MutableData mutableData) {
                BookingSlot s = mutableData.getValue(BookingSlot.class);
                if (s == null) {
                    //Upload new BookingSlot
                    dbRef.child(String.valueOf(selectedDate.getTime())).setValue(s);

                          );
                    }
                    return Transaction.success(mutableData);
                } else {
                    Toast.makeText(BookingActivity.this,"Slot has just been booked!",Toast.LENGTH_LONG).show();
                    //The chosen time has just been booked,

                }
                return Transaction.abort();
            }

            @Override
            public void onComplete(DatabaseError databaseError, boolean b,
                                   DataSnapshot dataSnapshot) {
                // Transaction completed
                Log.e("Booking", "postTransaction:onComplete:" + databaseError);
            }
        });

我在这里做错了吗?

标签: androidfirebasefirebase-realtime-database

解决方案


您的代码似乎假设启动事务可以锁定某个位置,但这不是 Firebase 中事务的工作方式。相反,Firebase 中的事务使用比较和设置逻辑:客户端告诉您(它认为)当前值是什么,然后您通过返回新值告诉它在这种情况下新值变成什么。

.setValue(s)因此,您应该在以下位置返回s,而不是调用该位置MutableData

dbRef.child(String.valueOf(selectedDate.getTime())).runTransaction(new Transaction.Handler() {
    @Override
    public Transaction.Result doTransaction(MutableData mutableData) {
        BookingSlot s = mutableData.getValue(BookingSlot.class);
        if (s == null) {
            mutableData.setValue(uid); // TODO: pass in the UID of the user who's claiming this slot
            return Transaction.success(mutableData);
        } else {
            Toast.makeText(BookingActivity.this,"Slot has just been booked!",Toast.LENGTH_LONG).show();
            return Transaction.abort();
        }
    }

    @Override
    public void onComplete(DatabaseError databaseError, boolean b, DataSnapshot dataSnapshot) {
        Log.e("Booking", "postTransaction:onComplete:" + databaseError);
    }
});

使用上面的代码,客户端不会覆盖彼此的值。

但是使用 Firebase,您必须始终考虑恶意用户可能会针对您的数据库编写自己的代码,因为他们可以在您的应用的 APK 中找到配置数据。因此,您还应该在 Firebase 的服务器端安全规则中强制每个插槽只能被声明一次。

如果预订存储在 下/bookings,可以通过以下方式完成:

{
  "rules": {
    "bookings": {
      "$timeslot": {
        ".write": "data.val() === null || data.val() === auth.uid"
      }
    }
  }
}

如果尚无任何值(尚未声明该插槽),或者如果用户写入是之前声明该插槽的用户(这将允许他们清除该插槽),则这将允许写入。


推荐阅读