25

我想知道当用户在与表单相同的页面上刷新时,其他程序员使用哪些方法/预防措施来阻止将数据两次输入到 MySQL 数据库中?显然它发生了,我需要一个好的方法来阻止它。

4

16 回答 16

40

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

永远不要用正文来响应 POST 请求。总是做这项工作,然后用 Location: 标头响应以重定向到更新的页面,以便浏览器使用 GET 请求它。

这样,清爽不会对您造成任何伤害。

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

于 2009-03-12T12:58:44.480 回答
7

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

于 2009-03-12T12:57:44.637 回答
5

伊利亚的回答是正确的,我只是想补充一点,而不是评论:

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

在表单页面上:

<?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

表单提交一次后,不能再次提交,除非用户故意填写第二次。

于 2009-05-01T16:59:18.290 回答
4

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

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

    INSERT INTO ... ON DUPLICATE KEY UPDATE ...
于 2009-03-12T13:55:00.880 回答
2

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

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

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

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

好的编程是基于分层你的工作,而不是依赖于一件事情来工作。尽管编码人员依赖增量 ID 是多么普遍,但它们是构建表的最懒惰的方式之一。

于 2009-03-12T19:21:34.907 回答
2

我同意 Ilya 的观点,并补充说,一旦单击“提交”按钮,您应该使用一些客户端 javascript 来禁用它,或者显示一个模式对话框(css 可以在此处为您提供帮助)以避免多次单击提交按钮。

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

希望能给你一些想法。

于 2009-03-12T13:07:10.433 回答
2

您可能想查看大多数现代 Web 应用程序实现的 POST/Redirect/GET 模式,请参阅http://en.wikipedia.org/wiki/Post/Redirect/Get

于 2009-03-12T14:38:02.430 回答
1

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

于 2009-03-13T13:49:31.590 回答
1

您必须在 showAddProductForm() 方法中将一个 uniqid 变量传递到您的 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 代码中放置一个隐藏的输入,其中 uniqid 的值已从 showAddProductForm 方法传递到 HTML 中。

<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

于 2017-04-19T09:48:05.083 回答
0

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

Response.Write("<script>location.href='yourpage.aspx'</script>");
于 2009-04-03T20:45:16.260 回答
0

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

于 2009-04-03T20:52:47.617 回答
0

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

更复杂的是,您可以在每个表单中放置一个隐藏的一次性使用密钥,一旦提交了具有该密钥的表单,当他们尝试提交具有相同密钥的表单时会显示错误。要创建此密钥,您可能需要使用 GUID 之类的东西。

于 2009-03-12T12:57:48.397 回答
0

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

于 2009-03-12T13:13:05.240 回答
0

您可以使用令牌来防止页面再次被处理!很多网络框架中都使用了这样的程序!

您应该使用的模式是“同步器令牌模式”!如果您有一个面向服务的应用程序,您可以将您的状态保存在数据库中。

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

你还应该看看那些开箱即用的库支持这样的东西!Grails就是这样一个!

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

<g:form useToken="true">

...

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

..

于 2009-03-12T13:23:42.033 回答
0

POE (Post Once Exactly)是一种 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'])) {

...允许多个随机数(nonci?noncei?)。

于 2009-08-17T11:32:39.160 回答
0

我的两分钱:

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

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

于 2009-03-12T17:17:54.377 回答