Как сделать назначение тикеров PHP более надежным? - PullRequest
1 голос
/ 28 июня 2019

Я создаю систему продажи билетов. Я хочу, чтобы каждая запись была уникальным числом между указанным диапазоном. Я создал функции, которые я включу, чтобы показать, как я обрабатываю вещи. Если я покупаю все билеты за один раз, функция обычно имеет 3 или 4 повторяющихся номера.

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

Взятые билеты, которые возвращают массив уже занятых чисел

function takenNumbers($drawID){
    $connect = new mysqli("localhost", "root", "", "dream");
    $stmt = $connect->prepare("SELECT * FROM transaction WHERE DrawID = ?");
    $stmt->bind_param("i", $drawID);
    $stmt->execute();
    $result = $stmt->get_result();

    $tickets = array();
    while($row = $result->fetch_assoc()){
        $id = $row['ID'];
        $tickets[] = $row['TicketNumber'];
    }

    return $tickets;
}

Числа, которые не взяты, также возвращаются в массив

function freeNumbers($drawID){
    $minTickets = 1;
    $maxTickets = totalTickets($drawID);
    $takenNumbers = takenNumbers($drawID);
    $freeNumbers = array();
    $allTickets = range(1, $maxTickets);
    $freeNumbers = array_values(array_diff($allTickets, $takenNumbers));

    return $freeNumbers;  
}

Тогда у меня есть генератор случайных билетов, основанный на этих функциях

function randomTicket($drawID){
    $num = freeNumbers($drawID);
    $random = array_rand($num, 1);
    return $random;
}

После обработки платежа я вызываю эту функцию для вставки в базу данных вместе с randomTicket.

for($i = 0; $i < $quantity; $i++){
    echo paymentMade($paymentId, $token, $payerID, $drawID) . "<br>";
}

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

1 Ответ

1 голос
/ 29 июня 2019

Ваша база данных является источником правды здесь, это хранилище всех возможных номеров билетов и билетов, которые были назначены.

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

Тем не менее, онобыло бы лучше найти решение базы данных для выдачи этих билетов.

Первое, что вы должны реализовать, - это UNIQUE INDEX в вашей таблице транзакций, которая записывает номера билетов, тогда БД будет поддерживать целостностьВаши номера всегда:

Вы не предоставили полную схему, поэтому я предполагаю, что в вашей таблице transaction есть столбцы DrawID и TicketNumber

CREATE UNIQUE INDEX UX_DrawTicketNumbers
ON transaction (DrawID,TicketNo);

Теперь, когда вы вставите дублирующий номер заявки, БД не выполнит операцию.Основной способ начать - просто обработать сбой и использовать текущую логику, чтобы повторять попытки для следующего номера, пока он не заработает.

Вы можете запросить следующий номер заявки непосредственно из базы данных, используя CTE:

WITH поддерживается в MySQL начиная с версии 8 (выпущено в 2016 году), если вы используете более старую версию, вы можете заполнить конкретную или временную таблицу всеми возможными номерами билетов, которые можно использовать вместоэто WITH предложение


  SET @DrawID := 1; -- set your drawId here
  SELECT TotalTickets into @MaxTicketNo FROM Draw WHERE ID = @DrawID;
  WITH RECURSIVE TicketNumbers (n) AS
  (
    SELECT 1
    UNION ALL
    SELECT n + 1 FROM TicketNumbers WHERE n < @MaxTicketNo
  )
  SELECT n  FROM TicketNumbers 
  WHERE n NOT IN (SELECT TicketNo FROM transaction WHERE DrawID = @DrawID)
  ORDER BY RAND() LIMIT 1;

По умолчанию MYSQL ограничивает рекурсию до 1000, поэтому вам может потребоваться установить предел рекурсии текущего сеанса на число, большее или равное totalTickets

SET SESSION cte_max_recursion_depth = 1000000;

Теперь вы можете использовать атомарность базы данных, чтобы взять результат вышеупомянутого запроса и использовать его непосредственно в ваших операторах вставки, в SQL Server с использованием CTE я бы просто сделал нечто подобноедля этого для обновления транзакции: (я не могу заставить его работать в БД Fiddle, но концепция должна работать)

SET @paymentId := 1, @token := 'XF-BankResponseNo', @payerID := 234, @drawID := 1;
SELECT TotalTickets into @MaxTicketNo FROM Draw WHERE ID = @DrawID;
WITH RECURSIVE TicketNumbers (n) AS 
(
  SELECT 1
  UNION ALL
  SELECT n + 1 FROM TicketNumbers WHERE n < @MaxTicketNo
)
INSERT INTO transaction (PaymentID, TokenID, PayerID, DrawID, TicketNo)
SELECT @paymentId, @token, @PayerID, @DrawID, n
FROM TicketNumbers
ORDER BY Rand() LIMIT 1;

tl; dr;

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

...