You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Firebase手机号/Google登录重复创建用户致资料丢失问题求助

Firebase多登录方式导致用户ID重复、资料丢失的解决方案

问题场景

使用Firebase手机号OTP和Google认证时,用户退出后再次登录,系统会生成新的用户ID,之前通过资料更新修改的信息被重置为初始状态,导致资料丢失。

问题根源

  1. 多登录方式未关联:同一用户使用手机号和Google登录时,Firebase Auth默认会创建两个独立的用户ID,用户数据分散在不同的Firestore文档中。
  2. 数据写入逻辑错误:每次登录都调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 07:30:49