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

Firebase注册系统问题:用户名已占用仍可完成注册

解决用户名已占用仍能注册的问题

嘿,我一眼就看出问题所在了——Firebase的数据库查询是异步操作,你原来的代码逻辑顺序完全错了!

问题根源

你写的usernamesRef.observeSingleEvent是异步执行的,它的回调块会在Firebase返回数据之后才运行,但你在调用这个方法之后,立刻就执行了self.usernameVerified = true这些代码。这就导致:不管用户名是否被占用,usernameVerified都会被强制设为true,注册流程自然会继续走下去。

而且还有个小问题:你现在是拉取所有用户数据来比对用户名,这在用户量变大之后会非常低效,还浪费带宽。

解决方案

我们需要把用户名校验改成异步回调的方式,同时优化Firebase的查询逻辑,直接检查用户名是否存在,而不是拉取所有数据。

1. 重构用户名校验函数

先把isValidUsername改成带回调闭包的函数,这样我们能在校验完成后再处理后续逻辑:

func isValidUsername(username: String, completion: @escaping (Bool) -> Void) {
    let ref = Database.database().reference(fromURL: "https://heytest.firebaseio.com/")
    // 优化:直接查询是否存在该用户名的用户,无需拉取全部数据
    let usersRef = ref.child("users")
    
    usersRef.queryOrdered(byChild: "username")
           .queryEqual(toValue: username)
           .observeSingleEvent(of: .value) { snapshot in
        if snapshot.exists() {
            // 用户名已被占用,更新UI并返回false
            self.usernameLabel.textColor = UIColor.red
            self.usernameLabel.text = "USERNAME TAKEN"
            self.usernameTextField.layer.addBorder(edge: UIRectEdge.bottom, color: UIColor.red, thickness: 1.5)
            completion(false)
        } else {
            // 用户名可用,恢复UI并返回true
            self.usernameLabel.textColor = UIColor.black
            self.usernameLabel.text = "USERNAME"
            self.usernameTextField.layer.addBorder(edge: UIRectEdge.bottom, color: UIColor.black, thickness: 1.5)
            completion(true)
        }
    } withCancel: { error in
        print(error.localizedDescription)
        completion(false)
    }
}

2. 修改注册按钮的点击逻辑

因为用户名校验是异步的,我们需要等校验结果回来后,再判断是否执行注册流程。这里先做同步的邮箱和密码校验,再处理异步的用户名校验:

@IBAction func onSignUp(_ sender: Any) {
    print("Sign Up pressed")
    
    // 先校验输入不为空
    guard let inputUsername = usernameTextField.text, !inputUsername.isEmpty,
          let inputEmail = emailTextField.text, !inputEmail.isEmpty,
          let inputPassword = passwordTextField.text, !inputPassword.isEmpty else {
        errorLabel.alpha = 1
        shake(viewToShake: errorLabel)
        print("Please fill in all required fields!")
        return
    }
    
    // 同步校验邮箱和密码
    let isEmailValid = isValidEmail(email: inputEmail)
    let isPasswordValid = isValidPassword(password: inputPassword)
    
    print("[SIGN UP] - Email: \(isEmailValid)")
    print("[SIGN UP] - Password: \(isPasswordValid)")
    
    // 如果邮箱或密码不合法,直接提示
    if !isEmailValid || !isPasswordValid {
        errorLabel.alpha = 1
        shake(viewToShake: errorLabel)
        print("Password/Email verification failed!")
        return
    }
    
    // 异步校验用户名
    isValidUsername(username: inputUsername) { [weak self] isUsernameValid in
        guard let self = self else { return }
        
        print("[SIGN UP] - Username: \(isUsernameValid)")
        
        if isUsernameValid {
            // 所有校验通过,开始注册流程
            Auth.auth().createUser(withEmail: inputEmail, password: inputPassword) { authResult, error in
                if let error = error {
                    self.errorLabel.alpha = 1
                    self.errorLabel.text = error.localizedDescription
                    self.shake(viewToShake: self.errorLabel)
                    return
                }
                
                guard let user = authResult?.user else { return }
                
                // 保存用户信息到数据库
                let ref = Database.database().reference(fromURL: "https://heytest.firebaseio.com/")
                let usersReference = ref.child("users").child(user.uid)
                let userValues = ["username": inputUsername, "email": inputEmail, "games-played": "0"]
                
                usersReference.updateChildValues(userValues) { err, _ in
                    if let err = err {
                        print(err.localizedDescription)
                        return
                    }
                    
                    print("[SIGN UP] - Successfully Signed Up")
                    self.errorLabel.alpha = 0
                    // 跳转到TabBar页面
                    if let tabBarVC = self.storyboard?.instantiateViewController(withIdentifier: "TabBarViewController") {
                        self.present(tabBarVC, animated: false)
                    }
                }
            }
        } else {
            // 用户名已被占用,提示用户
            self.errorLabel.alpha = 1
            self.shake(viewToShake: self.errorLabel)
            print("Username verification failed! Username is taken.")
        }
    }
}

额外注意点

  • 使用[weak self]是为了避免循环引用,防止内存泄漏;
  • 直接查询用户名的方式比拉取所有用户数据高效得多,还能避免暴露不必要的用户数据;
  • 先做非空校验和同步校验,能提前拦截无效输入,减少不必要的异步请求。

内容的提问来源于stack exchange,提问作者BlueBear

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 09:17:16