【问题标题】:Prevent duplicate record insert on manual page refresh防止在手动页面刷新时插入重复记录
【发布时间】:2011-04-13 05:19:45
【问题描述】:

我有一个刚刚浮出水面的问题。

我在 MVC 环境中工作。我的接口类中的方法名称与请求模块和操作的名称相匹配,即?module=test&action=action 将产生一个名为public function test_action() { }的方法

在这个特定的问题中,我有一个提交给自身的表单。如果验证通过,则会创建一条记录,然后我会显示另一个模块的模板。该模块需要一系列 post 变量,因为它在两个模块中使用。

我遇到的问题是,如果用户成功通过验证并尝试 F5 页面,则会创建另一条新记录等。

如何防止这种情况发生?

通常我会在成功插入后重定向标题,但在这种情况下我不能。

【问题讨论】:

  • 删除了重复,因为他确实声明他无法重定向。我会说没有重定向,这或多或少是不可能的。如果你不能做一个标题(或者它给你错误)你可以尝试 output_buffering 或者做一个 meta / javascript 重定向。

标签: php refresh


【解决方案1】:

我会采取完全不同的方式。我什至发现重定向是一种不正确的处理方式,因为更改位置并不是为了克服逻辑/表单问题。

正确的解决方法是:

  • 在隐藏输入中为表单添加唯一的哈希
  • 将哈希存储在服务器端会话中
  • 发送表单时,使用服务器上的哈希验证隐藏的输入哈希
  • 仅当表单验证正确时才执行行插入。

如果您正在使用 Zend Framework,则有一个 Zend_Form_Element_Hash 类适合您。

【讨论】:

  • Zend_Form_Element_Hash 不防止重复提交,只是防止 CSRF 攻击。
  • 我不同意,Zend_Form_Element_Hash - 不管它的目的是什么 - 完美地实现了这个目标:防止意外的表单执行。欺骗或意外无关紧要。
【解决方案2】:

开发者错误:

您需要创建一个处理程序页面:

  • 验证发送的数据
  • 插入行
  • 重定向用户

【讨论】:

    【解决方案3】:

    成功插入后,您可以/应该重定向到新页面。

    当你在 MVC 中工作时,你可以添加一个新的控制器来调用你想要显示的视图。

    【讨论】:

    • 我不能因为页面之后期望以帖子的形式提供信息。我不想在 get 中包含可以解决问题的信息。
    • @user275074 如果它期望以帖子的形式出现,请将其更改为使用会话。然后您只需将该数据存储在会话中,因为可以轻松修改 GET / POST 数据。会话,这有点困难,因为数据存储在服务器端。
    • @premiso,正是我的想法!
    【解决方案4】:

    这就是我所做的。这对我有用。希望它可以帮助其他人。

    //+++ start token +++
    //This is to prevent duplicate entry on page reload (F5). 19.   If I enter all values, press Record in journal and then press F5, the same values are recorded one more time. Need to prevent
    
    // 3rd. $token_hash_from_input get value of input field name name="' .$_SESSION['token_hash'] .'"
    $token_hash_from_input = $_SESSION['token_hash'];
    //echo $token_hash_from_input .' token_hash_from_input<br>';
    //echo $_POST[$token_hash_from_input] .' $_POST[$token_hash_from_input]<br>';
    //var_dump($token_hash, $_POST);
    
    // 4th. $_SESSION['token'] created/set in 1st. Then it as if goes around (at first to input field then after page reload returns here). However $_POST[$token_hash_from_input] is value received directly from input field. User click post and input field value is passed to $_POST[$token_hash_from_input]. Here I compare both.
    if ( $_SESSION['token'] != htmlspecialchars($_POST[$token_hash_from_input]) ) {
    $token_error .= 'yes';
    //echo 'session token and token from input field are not the same <br> ';
    }
    else {
    //echo 'session token is equal to post$token_hash)<br>';
    }
    
    // 1st. Create token and pass it to session
    $token = sha1(uniqid(mt_rand(), true));
    $_SESSION['token'] = $token;
    //echo $_SESSION['token'] .' new $_SESSION[token]<br>';//after each page reload new token created. Then this token passed to input form (hidden field). value="' .$_SESSION['token'] .'"
    
    // 2nd. Create token_hash and pass it to session. Token hash is to name input fields name and id. I may not use $token_hash and $_SESSION['token_hash']. Instead of this I can use name="token" and id="token".
    $token_hash = sha1(uniqid($time_when_form_submitted .'token' .$_SERVER["REMOTE_ADDR"]));
    //echo $token_hash .' $token_hash<br>';
    $_SESSION['token_hash'] = $token_hash;
    //echo $_SESSION['token_hash'] .' new SESSION$token_hash<br>';
    // +++ end token +++
    

    这样的输入框

    <input type="hidden" name="' .$_SESSION['token_hash'] .'" id="' .$_SESSION['token_hash'] .'" value="' .$_SESSION['token'] .'">
    

    <input type="hidden" name="<?php echo $_SESSION['token_hash'] ?>" id="<?php echo $_SESSION['token_hash'] ?>" value="<?php echo $_SESSION['token'] ?>">
    

    我想代码可以改进(我没有很好的知识 php 等)

    【讨论】:

      【解决方案5】:

      “通常我会在成功插入后重定向标题,但在这种情况下我不能。”

      您在这样做时遇到了一些错误吗?

      【讨论】:

      • 这是一个问题。通常问题放在 cmets 中。
      【解决方案6】:

      如果由于某种原因您无法重定向(这听起来很奇怪),您可以使用“相同” 用于在成功插入后刷新表单的数据验证机制。

      但这是一个非常丑陋的方法。

      【讨论】:

      • 如果下一页需要一些帖子数据,我该如何更改?
      • 我不确定我是否明白你在说什么。编辑:如果它像一个调查问卷,您可以查询您刚刚插入的数据或将其存储在 $_SESSION 中。
      【解决方案7】:

      许多 Web 开发人员在其 Web 应用程序中面临的最常见问题之一是,重复记录会在页面刷新时插入到数据库中。如果网页包含一些文本框和一个将文本框数据提交到数据库的按钮。在这种情况下,当用户在文本框中插入一些数据并单击提交按钮时,它会将记录保存到数据库中,然后如果用户立即刷新网页,那么相同的记录会再次保存到数据库中,因为有没有可以用来验证数据是否存在的唯一键,从而防止多次插入。

      从这个行为我们可以肯定地知道,在页面新鲜的按钮点击事件被触发。 为了避免这个问题,我们可以尝试下面讨论的这种方法。

      在页面加载事件中,将日期/时间戳保存在会话变量中,当页面首次加载时,会话变量将填充当前日期/时间,如下所示:

      *void Page_Load(Object sender, EventArgs e) { if(!IsPostBack) { 会话[“更新”] = Server.UrlEncode(System.DateTime.Now.ToString()); } }*

      在页面的 PreRender 事件中,一个 ViewState 变量被设置为 Session 变量的值,如下:

      void Page_PreRender(object obj,EventArgs e)
      {
          ViewState["update"] = Session["update"];
      }
      

      然后在运行数据库 INSERT 命令之前将这两个值相互比较。 如果它们相等,则允许执行该命令并使用当前日期/时间更新 Session 变量,否则将绕过该命令,如下所示:

      void btnSubmit_Click(object obj, EventArgs e)
      {
          string name = "";
          string qualification = "";
      
      
          if (Session["update"].ToString() == ViewState["update"].ToString())
          {
              if (txtName.Text != "" || txtName.Text != null)
              {
                  name = txtName.Text.ToString();
              }
      
              if (txtQualification.Text != "" || txtQualification.Text != null)
              {
                  qualification = txtQualification.Text.ToString();
              }
      
             //--- Insert data function should be execute here
      
             string strSql = "INSERT INTO Testdata (Name,Qualification) VALUES ('" + name + "','" + qualification + "')";
      
              SqlConnection ANConnection = new SqlConnection(ConnectionString);
      
              ANConnection.Open();
              SqlCommand ANCommand = new SqlCommand(strSql, ANConnection);
              ANCommand.ExecuteNonQuery();
      
              ANConnection.Close();
              ANConnection.Dispose();
      
              //--End of save data 
      
             lblMessage.Text = "Inserted Record Sucessfully 
             Session["update"] = Server.UrlEncode(System.DateTime.Now.ToString());
          }
          else
          {
              lblMessage.Text = "Failure – Due to Page Refresh";
              txtName.Text = "";
              txtQualification.Text = "";
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2012-08-17
        • 2011-06-18
        • 1970-01-01
        • 1970-01-01
        • 2016-01-18
        • 1970-01-01
        • 2012-12-20
        • 2017-12-21
        • 2016-08-24
        相关资源
        最近更新 更多