You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Android Studio(Java)多用户共享Firebase Firestore集合的问题解决

解决Firestore多用户预约时段唯一性校验问题

你现在的核心问题确实是用用户ID作为BookingInformation集合的文档ID,这直接导致每个用户只能访问自己的预约文档,完全看不到其他用户的预约数据,自然无法检查时段是否重复。下面我会一步步帮你修改代码,实现跨用户的时段唯一性校验:

1. 第一步:调整文档ID生成逻辑

不要再用currentUserId作为文档ID,改用Firestore自动生成的随机唯一ID,这样所有用户的预约数据都会存放在同一个集合里,方便后续全局查询。修改添加数据的代码:

// 原代码(用用户ID做文档ID)
dataStore.collection("BookingInformation").document(currentUserId).set(reserveInfo)
// 修改为(自动生成唯一文档ID)
dataStore.collection("BookingInformation").add(reserveInfo)

2. 第二步:重构ViewModel,实现全局时段冲突查询

原来的ViewModel只查询当前用户的单个文档,现在要改成查询整个集合中是否存在与当前输入时段冲突的预约。我们新增一个专门的冲突检查方法,同时保留获取当前用户预约的方法:

public class NewBookingViewModel extends ViewModel {
    private static final String TAG = "Firelog";

    // 检查目标时段是否已被预约
    public LiveData<Boolean> checkTimeConflict(String date, String startTime, String endTime) {
        final MutableLiveData<Boolean> conflictLiveData = new MutableLiveData<>();
        FirebaseFirestore fstore = FirebaseFirestore.getInstance();

        // 查询条件:日期相同,且时段完全匹配(若要禁止重叠可修改逻辑,见下文)
        fstore.collection("BookingInformation")
                .whereEqualTo("Date", date)
                .whereEqualTo("StartTime", startTime)
                .whereEqualTo("EndTime", endTime)
                .get()
                .addOnCompleteListener(task -> {
                    if (task.isSuccessful()) {
                        // 查询到文档说明时段已被占用
                        conflictLiveData.postValue(!task.getResult().isEmpty());
                    } else {
                        Log.e(TAG, "查询时段冲突失败", task.getException());
                        conflictLiveData.postValue(false);
                    }
                });
        return conflictLiveData;
    }

    // 获取当前用户的所有预约记录(可选保留)
    public LiveData<List<BookingInfo>> getUserBookings() {
        final MutableLiveData<List<BookingInfo>> bookingsLiveData = new MutableLiveData<>();
        String userId = FirebaseAuth.getInstance().getCurrentUser().getUid();
        FirebaseFirestore fstore = FirebaseFirestore.getInstance();

        fstore.collection("BookingInformation")
                .whereEqualTo("UserId", userId)
                .get()
                .addOnCompleteListener(task -> {
                    if (task.isSuccessful()) {
                        List<BookingInfo> bookings = new ArrayList<>();
                        for (DocumentSnapshot doc : task.getResult()) {
                            BookingInfo info = doc.toObject(BookingInfo.class);
                            info.setBookingDocId(doc.getId()); // 保存文档ID方便后续修改/删除
                            bookings.add(info);
                        }
                        bookingsLiveData.postValue(bookings);
                    } else {
                        bookingsLiveData.postValue(null);
                    }
                });
        return bookingsLiveData;
    }
}

3. 第三步:修改Fragment逻辑,预约前先做冲突检查

在用户点击"预约"或"查询可用性"按钮时,先调用ViewModel的冲突检查方法,根据结果再执行后续操作:

修改查询可用性按钮逻辑

searchBtn.setOnClickListener(v -> {
    String inputDate = date.getText().toString();
    String inputStartTime = startTime.getText().toString();
    String inputEndTime = endTime.getText().toString();

    newBookingViewModel.checkTimeConflict(inputDate, inputStartTime, inputEndTime)
            .observe(getViewLifecycleOwner(), hasConflict -> {
                if (hasConflict) {
                    Toast.makeText(getActivity().getApplicationContext(),
                            "该时段已被占用,请选择其他时间", Toast.LENGTH_SHORT).show();
                    availabletextView.setText("时段已被占用");
                } else {
                    Toast.makeText(getActivity().getApplicationContext(),
                            "该时段可用,可直接预约", Toast.LENGTH_SHORT).show();
                    availabletextView.setText("时段可用");
                }
            });
});

修改预约按钮逻辑

addBtn.setOnClickListener(v -> {
    String inputDate = date.getText().toString();
    String inputStartTime = startTime.getText().toString();
    String inputEndTime = endTime.getText().toString();

    // 先检查时段冲突
    newBookingViewModel.checkTimeConflict(inputDate, inputStartTime, inputEndTime)
            .observe(getViewLifecycleOwner(), hasConflict -> {
                if (hasConflict) {
                    Toast.makeText(getActivity().getApplicationContext(),
                            "该时段已被预约,请重新选择", Toast.LENGTH_SHORT).show();
                } else {
                    // 无冲突则提交预约
                    final Map<String, Object> reserveInfo = new HashMap<>();
                    reserveInfo.put("Date", inputDate);
                    reserveInfo.put("StartTime", inputStartTime);
                    reserveInfo.put("EndTime", inputEndTime);
                    reserveInfo.put("UserId", currentUserId);

                    dataStore.collection("BookingInformation").add(reserveInfo)
                            .addOnCompleteListener(task -> {
                                if (task.isSuccessful()) {
                                    Toast.makeText(getActivity().getApplicationContext(),
                                            "预约成功!", Toast.LENGTH_SHORT).show();
                                    // 可在此处清空输入框或刷新界面
                                } else {
                                    Toast.makeText(getActivity().getApplicationContext(),
                                            "预约失败,请重试", Toast.LENGTH_SHORT).show();
                                }
                            });
                }
            });
});

4. 额外注意:创建Firestore复合索引

因为我们用到了多个whereEqualTo条件组合查询,Firestore会要求创建对应的复合索引。第一次运行查询时,Logcat会输出一个错误链接,点击链接即可跳转到Firestore控制台自动创建索引,按提示操作即可。

进阶优化:禁止时段重叠(而非仅完全匹配)

如果你的需求是禁止任何时段重叠(比如A用户预约10-12点,B用户不能预约11-13点),需要修改查询条件(注意时间格式必须是可排序的,比如HH:mm):

fstore.collection("BookingInformation")
        .whereEqualTo("Date", date)
        // 核心逻辑:当前预约的开始时间 < 已有预约的结束时间,且当前预约的结束时间 > 已有预约的开始时间
        .whereLessThan("StartTime", endTime)
        .whereGreaterThan("EndTime", startTime)
        .get()

内容的提问来源于stack exchange,提问作者user10309403

火山引擎 最新活动