Остановить ввод данных в базу данных дважды

Im совершенно новый для PHP, мне было интересно, какие методы/предупреждения используют другие программисты, чтобы остановить ввод данных дважды в базу данных MySQL, когда пользователь обновляется на той же странице, что и форма? Obviouly это происходит, и мне нужен хороший способ остановить это.

Спасибо, Бен

Ответ 1

Я называю это золотым правилом веб-программирования:

Никогда не отвечайте телом на POST-запрос. Всегда выполняйте работу, а затем отвечайте заголовком Location: чтобы перенаправить на обновленную страницу, чтобы браузер запрашивал ее с помощью GET.

Таким образом, обновление не принесет вам никакого вреда.

Кроме того, относительно обсуждения здесь в комментариях. Чтобы защитить от двойной проводки, скажем, случайное двойное нажатие кнопки "Отправить", сохраните md5() вашей формы в текстовом файле и сравните новые формы md5 с сохраненным. Если они равны, у вас двойной пост.

Ответ 2

Обработать форму, а затем перенаправить на страницу результатов. Перезагрузите, только обновляет страницу результатов.

Ответ 3

Ответ 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

После того, как форма была отправлена ​​один раз, она не может быть отправлена ​​повторно, если пользователь не намеренно заполняет ее во второй раз.

Ответ 4

Чтобы заявить очевидное (я еще не видел его здесь...): никогда не используйте GET для публикации данных, всегда используйте POST, таким образом пользователь, по крайней мере, получает предупреждение, если он или она пытается обновить/опубликовать страницу (по крайней мере, в Firefox, но я полагаю и в других браузерах).

Кстати, если вы не можете позволить себе иметь одни и те же данные дважды, вы также должны рассмотреть решение MySQL с уникальным ключом (может быть комбинацией полей) и:

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

Ответ 5

Я согласен с Илья и добавлю, что вы должны использовать какой-либо клиентский javascript, чтобы отключить кнопку "отправить" после нажатия или представить модальный диалог (css может помочь вам здесь), чтобы избежать множественного нажатия кнопки отправки.

Наконец, если вы не хотите, чтобы данные в вашей базе данных дважды, а затем проверьте свою базу данных для данных, прежде чем пытаться вставить их. Если вы разрешаете дублировать записи, но не хотите, чтобы быстрые повторные вставки из одного источника, я бы использовал поля времени/даты и поля IP-адреса, чтобы разрешить "блокировку" по времени в моем коде отправки, т.е. Если IP то же самое, и последнее время отправки было менее 5 минут назад, после чего не вставляйте новую запись.

Надеюсь, что вы получите несколько идей.

Ответ 6

Возможно, вы захотите проверить шаблон POST/Redirect/GET, который реализует большинство современных веб-приложений, см. http://en.wikipedia.org/wiki/Post/Redirect/Get

Ответ 7

В дополнение к уже упомянутым хорошим рекомендациям о том, чтобы убрать пользователя с страницы публикации, чтобы кнопка обновления и возврата была безвредна, другим слоем в улучшении хранения данных является использование UUID в качестве ключей в вашей таблице и пусть ваши приложения сгенерируют их.

Они также известны как GUID в мире Microsoft, а в PHP вы можете генерировать один через uniqid() в PHP. Это шестнадцатеричное значение 32 символа, которое вы должны хранить в шестнадцатеричном/двоичном формате столбца, но если таблица не будет использоваться в основном, чем CHAR (32), она будет работать.

Создайте этот идентификатор, когда вы показываете свою форму как скрытый ввод, и обязательно отметьте столбец базы данных помеченным как первичный ключ. Теперь, если пользователю удастся полностью вернуться на страницу публикации, INSERT завершится неудачно, потому что у вас не может быть дубликатов ключей.

Дополнительный бонус к этому - если вы сгенерируете UUID в коде, то после выполнения вставки вам никогда не понадобится использовать расточительные запросы, извлекающие ключ, который был сгенерирован, потому что вы уже это знаете. Это хорошая польза, когда вам нужно вставить дочерние элементы INSERT в другие таблицы.

Хорошее программирование основано на разделении вашей работы, а не на том, чтобы работать на одном. Несмотря на то, что для кодировщиков часто приходится полагаться на инкрементные идентификаторы, они являются одним из самых ленивых способов построения таблицы.

Ответ 9

Вы должны передать переменную uniqid в свой html внутри метода showAddProductForm() и тот же uniqid в ваш $_SESSION forexample:

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, который был передан в HTML из метода showAddProductForm.

<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, '');
    }
}

Может быть, вы спросите, почему массив токенов, почему бы не одна переменная. Потому что в admin Panel пользователи открывают несколько вкладок и вставляют их в несколько вкладок, поэтому этот алгоритм терпит неудачу, если они используют несколько вкладок.

Особая благодарность тому, кто выясняет этот метод malekloo

Ответ 10

Ну, прежде всего, чтобы свести к минимуму, вы должны сделать так, чтобы они должны были заполнить форму для ввода данных. Таким образом, по крайней мере, они получат этот приятный небольшой диалог подтверждения, если они действительно хотят его повторно отправить.

Чтобы усложнилось, вы можете поместить в каждую форму скрытый одноразовый ключ, а после того, как форма с этим ключом была отправлена, отобразите ошибку, когда они попытаются отправить форму с тем же ключом. Чтобы создать этот ключ, вы, вероятно, захотите использовать что-то вроде GUID.

Ответ 11

Добавьте скрытое поле со случайной строкой (например, созданное md5(uniqid())), создайте поле в базе данных для этой строки и сделайте UNIQUE.

Ответ 12

Вы можете использовать токен, чтобы предотвратить повторную обработку страницы! Такая процедура используется во многих веб-фреймворках!

Образец, который вы должны использовать, это "Шаблон маркера синхронизатора"! Если у вас есть сервисное приложение, вы можете сохранить свой статус в базе данных.

Данные могут быть отправлены через JavaScript или с помощью скрытого поля формы.

Вы также должны взглянуть на библиотеки с поддержкой ящика для таких вещей! Grails - такой!

Смотрите: http://www.grails.org/1.1-Beta3+Release+Notes ...

<g:form useToken="true">

...

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

..

Ответ 13

Мои два цента:

  • Перенаправить пользователя на ту же страницу после отправки данных и подтвердить if(isset($_POST['submit'])

Другая полезная информация для подобных случаев:

  • Посмотрите LOCK TABLES в MySQL

  • Отключить кнопки через JavaScript после первого щелчка

Ответ 14

Лучший способ избежать дублирования записи на странице обновления, после вставки записей в базу данных при нажатии кнопки просто добавьте эту строку:

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

Ответ 15

Попробуйте включить что-то в свои формы, чтобы предотвратить двойное подчинение, предпочтительно в то же время защищая от подделки подпроса. Я рекомендую использовать то, что я называю formkey, который является одноразовым полем, которое однозначно идентифицирует отправку формы, привязывая ее к отдельному лицу пользователь по индивидуальному адресу. Эта концепция также относится к другим именам, но краткая заметка, которую я связал, объясняет это достаточно хорошо.

Ответ 16

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 - хорошее решение. Хотя сохранение nonce как дискретной переменной сеанса приведет к некоторым ошибкам, если клиент пытается выполнять одновременные сообщения из нескольких вкладок. Может быть, лучше...

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

... и...

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

..., чтобы допускать множественные числа (nonci? noncei?).