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

Flask-React注册页提交表单产生两次请求的问题排查

问题分析与解决方案

问题现象

  • 首次提交合法注册信息时,触发两次请求:POST返回500失败(payload可见),OPTIONS返回200成功(无响应数据),且前端表单字段未清除。
  • 再次提交相同信息(因用户名已存在被后端拒绝)时,仅产生POST请求,响应提示注册失败,表单字段正常清除。
  • 核心疑惑:未定义OPTIONS方法却出现该请求,且两次提交表现差异明显,已确认仅调用一次CORS。

后端代码

@auth.route('/sign-up', methods=['POST'])
@cross_origin()
def sign_up():
    data = request.json
    email = data.get('email')
    username = data.get('username')
    password1 = data.get('password1')
    password2 = data.get('password2')

    #### Validating Infomation
    # 检查用户是否已存在
    user = session.query(models.User).filter_by(username=username).first()
    print(user)
    if user:
        flash('Email already exists.', category='error')
        return jsonify({"error": "Signup unsuccessful"}), 500

    if len(email) < 4:
        flash('Email must be greater than 3 characters', category='error')
        return jsonify({"error": "Signup unsuccessful"}), 500
    elif len(username) < 4:
        flash('Username must be greater than 3 characters', category='error')
        return jsonify({"error": "Signup unsuccessful"}), 500
    elif password1 != password2:
        flash('Passwords don\'t match.', category='error')
        return jsonify({"error": "Signup unsuccessful"}), 500
    elif len(password1) < 7:
        flash('Password must be at least 7 characters', category='error')
        return jsonify({"error": "Signup unsuccessful"}), 500
    else:
        # 添加用户到数据库
        newUser = models.User(email=email, username=username, password=generate_password_hash(password1, method='scrypt'))
        session.add(newUser)
        session.commit()
        login_user(newUser, remember=True)
        flash('Account created!', category='success')

        return jsonify({
            "email": newUser.email,
            "username": newUser.username,
            "password": newUser.password
        }), 200

前端代码

const SignUp = () => {
    const [email, setEmail] = useState("");
    const [username, setUsername] = useState("");
    const [password1, setPassword1] = useState("");
    const [password2, setPassword2] = useState("");

    const handleSubmit = async (e) => {
        e.preventDefault();
    
        const data = {
          email,
          username,
          password1,
          password2,
        };
    
        try {
          const response = await fetch('http://localhost:5000/sign-up', {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json',
            },
            body: JSON.stringify(data),
          })
          
          if (response.ok) {
            const jsonData = await response.json();
            console.log(jsonData)
            setEmail("RIGHT");
          } else {
            setEmail("WRONG");
            console.error('Error:');
          }
        
          setUsername("");
          setPassword1("");
          setPassword2("");
        } catch (error) {
          console.error('Error:', error);
        }
          
      };
    return (
        <div>
            <div className = "signUpTitle">
                Begin your workout by signing up today!
            </div>

            <div className = "signUpCreds">
                    <p>
                    <label for = "signUpUsername">
                        Choose a Username (8-16 characters long):
                    </label>
                    <input 
                      type = "text" 
                      id = "signUpUsername" 
                      name = "signUpUsername" 
                      value={username}
                      onChange={(e) => setUsername(e.target.value)}
                    />
                    </p>

                    <p>
                    <label for = "signUpEmail">
                        Enter your email:
                    </label>
                    <input 
                      type = "text" 
                      id = "signUpEmail" 
                      name = "signUpEmail" 
                      value = {email}
                      onChange={(e) => setEmail(e.target.value)}
                    />
                    </p>
                
                    <p>
                    <label for = "signUpPassword">
                        Choose a Password (8-16 characters long):
                    </label>
                    <input 
                    type = "password" 
                    id = "signUpPassword" 
                    name = "signUpPassword" 
                    value = {password1}
                    onChange={(e) => setPassword1(e.target.value)}
                    />
                    </p>

                    <p>
                    <label for = "signUpPassword2">
                        Confirm Password:
                    </label>
                    <input 
                    type = "password" 
                    id = "signUpPassword2" 
                    name = "signUpPassword2" 
                    value = {password2}
                    onChange={(e) => setPassword2(e.target.value)}
                    />
                    </p>
              </div>

                <div className = "submitSignUp" onClick={handleSubmit}>
                    <input type="submit" value="Sign Up Now!" />
                </div>
        </div>
    )
}

export default SignUp

问题原因与解决

1. OPTIONS请求的由来

OPTIONS是浏览器自动发起的预检请求,当跨域请求属于「非简单请求」时触发——你的请求使用了Content-Type: application/json,不属于浏览器默认允许的简单请求类型,因此浏览器会先发送OPTIONS确认后端允许该跨域请求,之后才会发送实际的POST请求。这是正常的浏览器行为,无需手动定义OPTIONS路由。

2. 首次提交POST 500+表单未清除的原因

  • 后端报错点:创建用户后调用login_user(newUser, remember=True),前后端分离场景下,Flask-Login默认的Cookie会话机制会出现跨域兼容性问题,且如果User模型未正确实现Flask-Login要求的方法(如继承UserMixin),该方法会直接抛出异常,导致返回500状态码的HTML错误页面(而非JSON)。
  • 前端表单未清除:前端response.json()尝试解析HTML响应会报错,进入catch块,此时表单清空代码(setUsername("")等)位于try块末尾,不会被执行,因此字段保留。

3. 两次提交表现差异的原因

  • 首次提交:后端抛出异常返回HTML格式的500响应,前端解析失败进入catch,表单清空逻辑未执行。
  • 再次提交:后端检查到用户名已存在,直接返回JSON格式的500响应,前端进入else分支,表单清空逻辑正常执行。

具体修复步骤

  1. 修复后端登录逻辑:

    • 前后端分离场景建议改用JWT认证,替代Flask-Login的会话机制;
    • 暂时注释login_user(newUser, remember=True),先确保注册流程正常;
    • 确保User模型继承UserMixin以满足Flask-Login要求:
      from flask_login import UserMixin
      class User(UserMixin, db.Model):
          # 模型字段定义
      
  2. 修正用户存在检查的提示文本:

    if user:
        flash('Username already exists.', category='error')  # 原提示与检查逻辑不符,改为Username
        return jsonify({"error": "Signup unsuccessful"}), 500
    
  3. 前端优化表单清空逻辑:
    将清空代码移至finally块,确保无论请求成功、失败还是报错,都能清空表单:

    try {
      // 请求逻辑
    } catch (error) {
      console.error('Error:', error);
    } finally {
      setEmail("");
      setUsername("");
      setPassword1("");
      setPassword2("");
    }
    

    同时移除setEmail("RIGHT")/setEmail("WRONG")的测试代码,改用正常的状态提示组件。

  4. 优化CORS配置:
    全局配置CORS替代单路由配置,避免遗漏:

    from flask_cors import CORS
    CORS(app, supports_credentials=True)  # 若需携带Cookie则添加supports_credentials
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 05:48:07