面向建筑所有者的Firestore数据结构设计咨询(Android项目)
Firestore数据结构设计与功能实现方案
核心数据结构设计
Firestore是文档型数据库,建议分三个核心集合(避免超过2层嵌套,保证查询效率):
1. users 集合(存储所有用户)
文档ID直接用Firebase Auth生成的uid(和你已有的身份验证流程对齐),字段包括:
email: string(用户注册邮箱)role: string(可选值:building_owner/region_admin)regionId: string(仅区域管理员需要,关联所属区域ID)displayName: string(可选,用户昵称)
2. regions 集合(存储区域信息)
文档ID用自定义标识(比如region_001),字段包括:
name: string(区域名称,如“XX区”)adminId: string(关联users集合的管理员uid)
3. buildings 集合(存储建筑数据)
文档ID用Firestore自动生成ID或自定义ID,字段包括:
ownerId: string(关联users集合的建筑所有者uid)regionId: string(关联regions集合的区域ID)basicInfo: map(存储建筑详情:address、area、constructionYear等)status: string(可选值:pending_review/approved,用于管理员审核)createTime: timestamp(用服务器时间生成,记录上传时间)
用户与自身建筑的关联实现
1. 上传建筑时关联用户
用户上传建筑时,直接获取当前登录用户的uid,写入buildings文档的ownerId字段:
// 获取当前登录用户 FirebaseUser currentUser = FirebaseAuth.getInstance().getCurrentUser(); if (currentUser == null) { // 未登录处理逻辑 return; } // 构建建筑数据 Map<String, Object> buildingData = new HashMap<>(); buildingData.put("ownerId", currentUser.getUid()); buildingData.put("regionId", "region_001"); // 可让用户选择区域或自动定位填充 buildingData.put("basicInfo", new HashMap<String, Object>() {{ put("address", "XX街道XX号"); put("area", 120.5); }}); buildingData.put("status", "pending_review"); buildingData.put("createTime", FieldValue.serverTimestamp()); // 写入Firestore FirebaseFirestore db = FirebaseFirestore.getInstance(); db.collection("buildings") .add(buildingData) .addOnSuccessListener(documentReference -> { // 上传成功提示逻辑 }) .addOnFailureListener(e -> { // 上传失败处理逻辑 });
2. 查询用户自己的建筑
通过whereEqualTo过滤ownerId等于当前用户uid的文档:
FirebaseUser currentUser = FirebaseAuth.getInstance().getCurrentUser(); if (currentUser == null) return; FirebaseFirestore db = FirebaseFirestore.getInstance(); db.collection("buildings") .whereEqualTo("ownerId", currentUser.getUid()) .get() .addOnCompleteListener(task -> { if (task.isSuccessful()) { List<Building> userBuildings = new ArrayList<>(); for (QueryDocumentSnapshot document : task.getResult()) { // 解析文档为自定义Building实体类 Building building = document.toObject(Building.class); userBuildings.add(building); } // 渲染用户建筑列表 } });
3. 修改自身建筑的权限控制
修改前先验证当前用户uid是否等于建筑的ownerId,防止越权操作:
String buildingDocId = "xxx"; // 要修改的建筑文档ID FirebaseUser currentUser = FirebaseAuth.getInstance().getCurrentUser(); if (currentUser == null) return; FirebaseFirestore db = FirebaseFirestore.getInstance(); db.collection("buildings").document(buildingDocId) .get() .addOnSuccessListener(documentSnapshot -> { if (documentSnapshot.exists()) { String ownerId = documentSnapshot.getString("ownerId"); if (currentUser.getUid().equals(ownerId)) { // 权限验证通过,执行修改 Map<String, Object> updates = new HashMap<>(); updates.put("basicInfo.address", "新地址"); db.collection("buildings").document(buildingDocId) .update(updates) .addOnSuccessListener(aVoid -> { // 修改成功提示 }); } else { // 无权限修改提示 } } });
区域管理员接收数据的实现
1. 管理员查询所属区域的待审核建筑
管理员登录后,先从users文档获取自己的regionId,再过滤该区域下status为pending_review的建筑:
FirebaseUser currentUser = FirebaseAuth.getInstance().getCurrentUser(); if (currentUser == null) return; FirebaseFirestore db = FirebaseFirestore.getInstance(); // 先获取管理员所属区域ID db.collection("users").document(currentUser.getUid()) .get() .addOnSuccessListener(userDoc -> { String regionId = userDoc.getString("regionId"); // 实时监听该区域的待审核建筑 db.collection("buildings") .whereEqualTo("regionId", regionId) .whereEqualTo("status", "pending_review") .addSnapshotListener((querySnapshot, e) -> { if (e != null) return; for (DocumentChange dc : querySnapshot.getDocumentChanges()) { if (dc.getType() == DocumentChange.Type.ADDED) { Building newBuilding = dc.getDocument().toObject(Building.class); // 通知管理员有新建筑待审核 } } }); });
2. 管理员更新建筑审核状态
审核通过后,修改建筑的status字段:
String buildingDocId = "xxx"; FirebaseFirestore db = FirebaseFirestore.getInstance(); db.collection("buildings").document(buildingDocId) .update("status", "approved") .addOnSuccessListener(aVoid -> { // 可推送通知给建筑所有者 });
额外实用建议
- 配置安全规则:必须设置Firestore安全规则,禁止未授权访问:
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { // 建筑所有者仅能读写自己的建筑 match /buildings/{buildingId} { allow read, update, delete: if request.auth != null && resource.data.ownerId == request.auth.uid; allow create: if request.auth != null; } // 区域管理员仅能查看所属区域的建筑 match /buildings/{buildingId} { allow read: if request.auth != null && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.regionId == resource.data.regionId; } // 用户仅能读写自己的文档 match /users/{userId} { allow read, write: if request.auth != null && request.auth.uid == userId; } } } - 避免嵌套集合:不要把建筑放在用户文档的子集合里,否则跨区域查询会异常低效。
- 统一时间格式:用
FieldValue.serverTimestamp()记录时间,避免客户端时间不一致问题。
内容的提问来源于stack exchange,提问作者Tudor M.
相关产品推荐
相关产品推荐

