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




