iOS Firebase创建新用户:未登录时检查用户名与邮箱可用性
嘿,这个场景我在做iOS Firebase项目时刚好碰到过,给你分享两个靠谱的方案,都不用拉全量用户列表,而且能严格遵守你的安全权限要求👇
方案一:用Firebase Cloud Functions 做验证接口
这是最稳妥的方案,把检查逻辑放在后端云函数里,前端只需要调用接口拿结果,完全不用担心数据泄露。
- 第一步,先在Firestore里建两个集合:
usernames和emails,每个文档的ID分别是用户名和邮箱地址(文档内容可以是空,或者存个创建时间,核心用ID做唯一约束)。 - 第二步,写一个Callable云函数,接收前端传的邮箱和用户名,然后查询这两个集合对应的文档是否存在:
const functions = require("firebase-functions"); const admin = require("firebase-admin"); admin.initializeApp(); exports.checkUserCredentials = functions.https.onCall(async (data, context) => { const { email, username } = data; // 检查用户名是否被占用 const usernameSnapshot = await admin.firestore().collection('usernames').doc(username).get(); // 检查邮箱是否已注册 const emailSnapshot = await admin.firestore().collection('emails').doc(email).get(); if (usernameSnapshot.exists) { return { available: false, reason: "用户名已被占用" }; } if (emailSnapshot.exists) { return { available: false, reason: "邮箱已被注册" }; } return { available: true }; });
- 第三步,iOS前端调用这个云函数,处理返回结果:
import FirebaseFunctions // 初始化Functions实例 let functions = Functions.functions() // 封装检查方法 func checkCredentials(email: String, username: String, completion: @escaping (Bool, String?) -> Void) { functions.httpsCallable("checkUserCredentials").call([ "email": email, "username": username ]) { result, error in if let error = error as NSError? { // 处理云函数调用错误 completion(false, error.localizedDescription) return } guard let response = result?.data as? [String: Any] else { completion(false, "返回格式错误") return } let available = response["available"] as? Bool ?? false let reason = response["reason"] as? String completion(available, reason) } } // 调用示例 checkCredentials(email: "test@example.com", username: "testuser") { available, reason in if available { print("邮箱和用户名都可用") // 继续下一步创建用户流程 } else { print("不可用原因:\(reason ?? "未知错误")") // 更新UI提示用户 } }
- 最后别忘了配置安全规则,只允许云函数访问这两个集合:
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /usernames/{username} { allow read, write: if request.auth != null && request.auth.token.email == "your-service-account-email"; } match /emails/{email} { allow read, write: if request.auth != null && request.auth.token.email == "your-service-account-email"; } } }
方案二:Firestore 原子创建验证(无需云函数)
如果不想写云函数,这个轻量方案也可行,利用Firestore文档ID的唯一性和创建操作的原子性来间接验证。
- 第一步,同样建
usernames和emails集合,文档ID用用户名/邮箱。 - 第二步,配置安全规则,允许未登录用户尝试创建空文档(用来验证可用性),同时允许删除临时文档:
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /usernames/{username} { // 只允许创建空文档 allow create: if request.resource.data.size() == 0; // 允许删除临时文档,也可以加时间限制更安全 allow delete: if true; } match /emails/{email} { allow create: if request.resource.data.size() == 0; allow delete: if true; } } }
- 第三步,iOS端尝试创建临时文档,如果创建成功说明可用,然后删除临时文档;如果失败(错误码5,代表文档已存在)说明被占用:
import FirebaseFirestore let db = Firestore.firestore() // 检查用户名可用性 func checkUsernameAvailability(username: String, completion: @escaping (Bool) -> Void) { let docRef = db.collection("usernames").document(username) docRef.setData([], merge: false) { error in if let error = error { // 错误码5代表文档已存在,即用户名被占用 if (error as NSError).code == 5 { completion(false) } else { // 其他错误(比如权限问题),默认返回不可用 completion(false) } } else { // 创建成功,说明可用,删除临时文档 docRef.delete { _ in completion(true) } } } } // 检查邮箱可用性同理 func checkEmailAvailability(email: String, completion: @escaping (Bool) -> Void) { let docRef = db.collection("emails").document(email) docRef.setData([], merge: false) { error in if let error = error, (error as NSError).code == 5 { completion(false) } else { docRef.delete { _ in completion(true) } } } } // 调用示例 checkUsernameAvailability(username: "testuser") { available in if available { print("用户名可用") } else { print("用户名已被占用") } }
重要注意事项
- 一定要在用户最终创建账号时再做一次验证!因为检查和创建之间可能有时间差,比如用户A检查完可用,在创建账号前,用户B抢先注册了同一个用户名/邮箱,所以创建账号时必须再确认一次,避免冲突。
- 用临时文档方案时,要处理好并发情况,比如两个用户同时检查同一个用户名,只会有一个创建成功,这是Firestore原子性保证的,属于正常情况。
- 安全规则可以根据你的需求再细化,比如临时文档的删除可以限制只能在创建后的5分钟内进行,防止恶意删除他人的正式文档。
内容的提问来源于stack exchange,提问作者AJGronevelt
相关产品推荐
相关产品推荐

