Прекратить вставку данных в базу данных дважды - PullRequest
25 голосов
/ 12 марта 2009

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

Спасибо, Бен

Ответы [ 16 ]

40 голосов
/ 12 марта 2009

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

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

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

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

7 голосов
/ 12 марта 2009

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

5 голосов
/ 01 мая 2009

Илья ответил правильно, я просто хотел добавить чуть больше, чем уместилось бы в комментарии:

Если повторная отправка опасна (возврат и повторная отправка, перезагрузка страницы результатов [если вы не воспользовались советом Ильи] и т. Д.), Я использую «nonce», чтобы убедиться, что форма может пройти только один раз. 1003 *

На странице формы:

<?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 голосов
/ 12 марта 2009

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

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

    INSERT INTO ... ON DUPLICATE KEY UPDATE ...
2 голосов
/ 12 марта 2009

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

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

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

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

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

2 голосов
/ 12 марта 2009

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

2 голосов
/ 12 марта 2009

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

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

Надеюсь, что это дает вам некоторые идеи.

1 голос
/ 19 апреля 2017

Вы должны передать переменную uniqid в свой html внутри метода showAddProductForm () и тот же 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 внутри вашей формы со значением уникального идентификатора, который был передан в 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, '');
    }
}

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

Отдельное спасибо, кто понял этот метод malekloo

1 голос
/ 13 марта 2009

Я обычно полагаюсь на ограничение индекса UNIQUE sql. http://dev.mysql.com/doc/refman/5.0/en/constraint-primary-key.html

0 голосов
/ 17 августа 2009

POE (Post Once точно) - это шаблон HTTP, направленный на предупреждение клиента о блокировке двойной отправки с использованием собственного заголовка ...

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

... но все еще в спецификации.

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

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

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

... и ...

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

... чтобы разрешить множественные одноразовые номера (nonci? Noncei?).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...