停止数据插入数据库两次

时间:2009-03-12 12:55:07

标签: php user-input

我是PHP的新手,我想知道当用户在与表单相同的页面刷新时,其他程序员使用什么方法/防止将数据输入MySQL数据库两次?显然它发生了,我需要一个很好的方法来阻止它。

谢谢,Ben

16 个答案:

答案 0 :(得分:40)

我称之为网络编程的黄金法则:

永远不要用身体回应POST请求。始终执行工作,然后使用Location:标头进行响应以重定向到更新的页面,以便浏览器通过GET请求它。

这样,刷新对你没有任何伤害。

另外,关于评论中的讨论。为防止双重发布,例如,意外双击“提交”按钮,将表单的md5()存储在文本文件中,并将新表单的md5与存储的md5进行比较。如果他们是平等的,你就有一个双重职位。

答案 1 :(得分:7)

处理表单,然后重定向到结果页面。然后重新加载,只重新显示结果页面。

答案 2 :(得分:5)

Ilya的回答是正确的,我只想添加一些比评论更多的内容:

如果重新提交是危险的(返回并再次提交,重新加载结果页面[如果你没有采取Ilya的建议]等)我使用“nonce”来确保表单只能通过一次。< / p>

在表单页面上:

<?php
@session_start(); // make sure there is a session

// store some random string/number
$_SESSION['nonce'] = $nonce = md5('salt'.microtime());
?>
// ... snip ...
<form ... >
<input type="hidden" name="nonce" value="<?php echo $nonce; ?>" />
</form>

在处理页面中:

<?php
if (!empty($_POST)) {
@session_start();

// check the nonce
if ($_SESSION['nonce'] != $_POST['nonce']) {
    // some error condition
} else {
    // clear the session nonce
    $_SESSION['nonce'] = null;
}

// continue processing

表单提交一次后,无法再次提交,除非用户故意将其填写第二次。

答案 3 :(得分:4)

说明显而易见(我还没有看到它......):永远不要使用GET发布数据,总是使用POST,这样用户至少会在他或她尝试刷新/重新发送时收到警告发布页面(至少在Firefox中,但我想在其他浏览器中也是如此)。

顺便说一句,如果您无法承担两次相同的数据,您还应该考虑使用唯一密钥(可以是字段组合)的MySQL解决方案,并且:

    INSERT INTO ... ON DUPLICATE KEY UPDATE ...

答案 4 :(得分:2)

我同意那里的Ilya并补充一点,你应该使用一些客户端javascript来点击“提交”按钮,或者出现一个模态对话框(css可以帮到你),以避免多次点击提交按钮

最后,如果您不希望数据库中的数据两次,那么在尝试插入数据之前,还要检查数据库中的数据。如果您确实允许重复记录但不希望从单个来源快速重复插入,那么我会使用时间/日期戳和IP地址字段来允许我的提交代码中基于时间的“锁定”,即如果IP是相同的,上次提交时间不到5分钟,然后不插入新记录。

希望能给你一些想法。

答案 5 :(得分:2)

您可能希望查看大多数现代网络应用实施的POST / Redirect / GET模式,请参阅http://en.wikipedia.org/wiki/Post/Redirect/Get

答案 6 :(得分:2)

除了已经提到的关于让用户远离发布页面以便刷新和返回按钮无害的好建议之外,改善数据存储的另一个方面是使用UUID s作为表中的键并让您的应用程序生成它们。

这些在Microsoft世界中也称为GUID,在PHP中,您可以通过PHP中的uniqid()生成一个GUID。这是一个32个字符的十六进制值,您应该以十六进制/二进制列格式存储,但如果该表不会被大量使用,那么CHAR(32)将起作用。

将表单显示为隐藏输入时生成此ID,并确保将数据库列标记为主键。现在,如果用户确实设法一直回到发布页面,INSERT将失败,因为您没有重复的密钥。

额外的好处是,如果您在代码中生成UUID,那么在执行插入后,您将永远不需要使用浪费的查询来检索由于您已经知道它而生成的密钥。当您需要将子项插入其他表时,这是一个很好的好处。

良好的编程基于对工作进行分层,而不是依靠一件事来工作。尽管编码人员依赖增量ID是多么常见,但它们是构建表格最懒惰的方法之一。

答案 7 :(得分:1)

我通常依赖于sql UNIQUE索引Constraint。 http://dev.mysql.com/doc/refman/5.0/en/constraint-primary-key.html

答案 8 :(得分:1)

你必须将一个uniqid变量传递给showAddProductForm()方法中的html,并将相同的uniqid传递到你的$ _SESSION中:

public function showAddProductForm()
{
    $uniId = uniqid();
    $_SESSION['token'][$uniId] = '1';
    $fields['token'] = 'token['.$uniId.']';

    $this->fileName = 'product.add.form.php';
    $this->template($fields);
    die();
}

然后你必须在表单中的HTML代码中放入一个隐藏的输入,其中包含从showAddProductForm方法传递到HTML的uniqid的值。

<input type="hidden" name="<?=$list['token']?>" value="1">

在提交事件之后,您将在addProduct()方法的开头分析它。如果令牌存在于$ _SESSION中并且在该数组中具有相等的值,则这是新请求。将他重定向到正确的页面并取消设置令牌并继续插入。否则,它来自重新加载页面或重复请求,将其重定向到addProdeuct页面

public function addProducts($fields)
{
    $token_list = array_keys($fields['token']);
    $token = $token_list['0'];
    if (isset($_SESSION['token'][$token]) and $_SESSION['token'][$token] == '1') {
        unset($_SESSION['token'][$token]);
    } else {
        $this->addAnnounceForm($fields, '');
    }
}

也许你问为什么一个令牌数组为什么不是一个变量。因为在管理员面板中,用户打开多个选项卡并插入多个选项卡,因此如果他们使用多个选项卡,则此算法将失败。

特别感谢谁找出这种方法malekloo

答案 9 :(得分:0)

嗯,首先,要最小化,你应该这样做,所以他们必须做表格发布插入数据。这样,至少他们会得到一个很好的小确认对话框,询问他们是否真的要重新提交它。

为了使这个更复杂,您可以在每个表单中放置一个隐藏的一次性使用密钥,并且一旦提交了具有该密钥的表单,当他们尝试使用相同的密钥提交表单时显示错误。要创建此密钥,您可能希望使用类似GUID的内容。

答案 10 :(得分:0)

添加一个带有随机字符串的隐藏字段(例如由md5(uniqid())生成),在数据库中为该字符串创建一个字段并使其成为唯一。

答案 11 :(得分:0)

您可以使用令牌来阻止再次处理该页面! 这样的程序在很多Web框架中使用!

您应该使用的模式是“Synchronizer Token Pattern”! 如果您有面向服务的应用程序,则可以将状态保存在数据库中。

可以通过JavaScript或隐藏的表单字段发送数据。

您还应该看一下对此类内容提供开箱即用的图书馆! Grails就是这样的!

请参阅:http://www.grails.org/1.1-Beta3+Release+Notes ...

<g:form useToken="true">

...

withForm {
   // good request
}.invalidToken {
   // bad request
}

...

答案 12 :(得分:0)

我的两分钱:

  • 发送数据后,将用户重定向到同一页面并验证if(isset($_POST['submit'])

类似案例的其他有用信息:

答案 13 :(得分:0)

避免页面刷新时重复记录插入的最佳方法是, 单击按钮后在数据库中插入记录后,只需添加以下行:

Response.Write("<script>location.href='yourpage.aspx'</script>");

答案 14 :(得分:0)

尝试在表单中包含一些内容以防止重复提交,最好同时防止跨站点请求伪造。我建议使用我称之为 formkey 的内容,这是一个一次性使用的字段,用于唯一标识表单提交,将其绑定到单个地址的单个用户。这个概念也属于其他名称,但我所链接的简短说明解释得很好。

答案 15 :(得分:0)

POE (完全发布一次)是一种HTTP模式,旨在警告客户端使用专有标头阻止双提交......

GET /posts/new HTTP/1.1
POE: 1
...

......但仍处于规范中。

http://www.mnot.net/drafts/draft-nottingham-http-poe-00.txt

我认为上述现时是一个很好的解决方案。尽管如果客户端尝试从多个选项卡执行同时发布的帖子,将nonce存储为离散会话变量会引入一些错误。也许更好......

$_SESSION['nonces'][] = $nonce;

......和......

if (in_array($_POST['nonce'], $_SESSION['nonces'])) {

...允许多个nonce(nonci?noncei?)。