Firebase手机号/Google登录重复创建用户致资料丢失问题求助
Firebase多登录方式导致用户ID重复、资料丢失的解决方案
问题场景
使用Firebase手机号OTP和Google认证时,用户退出后再次登录,系统会生成新的用户ID,之前通过资料更新修改的信息被重置为初始状态,导致资料丢失。
问题根源
- 多登录方式未关联:同一用户使用手机号和Google登录时,Firebase Auth默认会创建两个独立的用户ID,用户数据分散在不同的Firestore文档中。
- 数据写入逻辑错误:每次登录都调用
set(users)或set(newUser),该方法会完全覆盖Firestore中已存在的文档,即使是同一用户,也会把之前更新的name、email、coins等字段重置为初始值。
解决方案
一、关联用户的多个登录方式
确保同一用户的手机号和Google账号关联到同一个Firebase Auth用户ID,避免生成多个用户ID:
- 用户首次用任意方式登录后,提供"绑定Google账号"或"绑定手机号"的入口,将两种登录方式关联到同一Auth账号。
- 绑定逻辑示例(以手机号绑定已登录的Google用户为例):
// 已通过Google登录,现在绑定手机号 PhoneAuthOptions options = PhoneAuthOptions.newBuilder(auth) .setPhoneNumber(phoneNumber) .setTimeout(120L, TimeUnit.SECONDS) .setActivity(this) .setCallbacks(new PhoneAuthProvider.OnVerificationStateChangedCallbacks() { @Override public void onVerificationCompleted(@NonNull PhoneAuthCredential credential) { // 关联手机号到当前登录用户 auth.getCurrentUser().linkWithCredential(credential) .addOnCompleteListener(linkTask -> { if (linkTask.isSuccessful()) { Toast.makeText(this, "手机号绑定成功", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "绑定失败:" + linkTask.getException().getMessage(), Toast.LENGTH_SHORT).show(); } }); } @Override public void onVerificationFailed(@NonNull FirebaseException e) { Toast.makeText(this, "验证失败:" + e.getMessage(), Toast.LENGTH_SHORT).show(); } @Override public void onCodeSent(@NonNull String verifyId, @NonNull PhoneAuthProvider.ForceResendingToken token) { // 处理验证码发送后的逻辑,比如输入验证码后调用linkWithCredential } }).build(); PhoneAuthProvider.verifyPhoneNumber(options);
二、修改Firestore数据写入逻辑,避免覆盖已有数据
方案1:使用set()的合并模式
调用set()时传入SetOptions.merge(),仅更新传入对象中的非空字段,不覆盖文档中已存在的其他字段:
手机号登录的verifyOTP方法修改:
private void verifyOTP(String otp) { PhoneAuthCredential credential = PhoneAuthProvider.getCredential(verificationId, otp); auth.signInWithCredential(credential).addOnCompleteListener(task -> { if (task.isSuccessful()) { FirebaseUser currentUser = auth.getCurrentUser(); if (currentUser == null) return; String uid = currentUser.getUid(); User user = new User(); user.setPhone(currentUser.getPhoneNumber()); database.collection("users") .document(uid) // 合并模式:仅更新phone字段,保留其他已有数据 .set(user, SetOptions.merge()) .addOnCompleteListener(dbTask -> { if (dbTask.isSuccessful()) { dialog.dismiss(); startActivity(new Intent(OTP_Activity.this, MainActivity.class)); finishAffinity(); } else { dialog.dismiss(); Toast.makeText(OTP_Activity.this, dbTask.getException().getLocalizedMessage(), Toast.LENGTH_SHORT).show(); } }); } else { dialog.dismiss(); Toast.makeText(OTP_Activity.this, task.getException().getLocalizedMessage(), Toast.LENGTH_SHORT).show(); } }); }
Google登录的firebaseAuthWithGoogle方法修改:
private void firebaseAuthWithGoogle(String idToken){ AuthCredential firebaseCredential = GoogleAuthProvider.getCredential(idToken, null); auth.signInWithCredential(firebaseCredential) .addOnCompleteListener(this, task -> { if (task.isSuccessful()){ FirebaseUser user = auth.getCurrentUser(); if (user == null) return; String uid = user.getUid(); User newUser = new User(); newUser.setName(user.getDisplayName()); newUser.setEmail(user.getEmail()); if (user.getPhotoUrl() != null) { newUser.setImage(user.getPhotoUrl().toString()); } database.collection("users") .document(uid) // 合并模式:仅更新name、email、image字段 .set(newUser, SetOptions.merge()) .addOnCompleteListener(dbTask -> { if (dbTask.isSuccessful()){ dialog.dismiss(); startActivity(new Intent(SignupActivity.this, MainActivity.class)); finishAffinity(); } else { dialog.dismiss(); Toast.makeText(SignupActivity.this, dbTask.getException().getLocalizedMessage(), Toast.LENGTH_SHORT).show(); } }); } else { dialog.dismiss(); Toast.makeText(SignupActivity.this, "登录失败", Toast.LENGTH_SHORT).show(); } }); }
方案2:先检查文档是否存在,不存在再创建
如果仅需在用户首次登录时创建初始数据,后续登录直接跳转:
// 以手机号登录为例 private void verifyOTP(String otp) { PhoneAuthCredential credential = PhoneAuthProvider.getCredential(verificationId, otp); auth.signInWithCredential(credential).addOnCompleteListener(task -> { if (task.isSuccessful()) { FirebaseUser currentUser = auth.getCurrentUser(); if (currentUser == null) return; String uid = currentUser.getUid(); database.collection("users").document(uid).get() .addOnCompleteListener(docTask -> { if (docTask.isSuccessful()) { DocumentSnapshot document = docTask.getResult(); if (!document.exists()) { // 首次登录:创建初始数据 User user = new User(); user.setPhone(currentUser.getPhoneNumber()); user.setCoins(500); // 手动设置初始金币,避免Java默认值不被Firestore识别 database.collection("users").document(uid).set(user) .addOnCompleteListener(createTask -> { dialog.dismiss(); if (createTask.isSuccessful()) { startActivity(new Intent(OTP_Activity.this, MainActivity.class)); finishAffinity(); } else { Toast.makeText(OTP_Activity.this, createTask.getException().getMessage(), Toast.LENGTH_SHORT).show(); } }); } else { // 已有用户:直接跳转 dialog.dismiss(); startActivity(new Intent(OTP_Activity.this, MainActivity.class)); finishAffinity(); } } else { dialog.dismiss(); Toast.makeText(OTP_Activity.this, docTask.getException().getMessage(), Toast.LENGTH_SHORT).show(); } }); } else { dialog.dismiss(); Toast.makeText(OTP_Activity.this, task.getException().getLocalizedMessage(), Toast.LENGTH_SHORT).show(); } }); }
额外注意事项
- User类中的
coins字段默认值为500,Firestore不会自动识别Java的默认值,首次创建用户时需手动调用user.setCoins(500),否则该字段不会被写入Firestore。 - 关联登录方式时,需处理"手机号/Google账号已被其他用户绑定"的场景,在
linkWithCredential的失败回调中提示用户解绑或登录已有账号。
内容的提问来源于stack exchange,提问作者Tayyba Rani
相关产品推荐
相关产品推荐

