如何使 PHP 代码分配更稳健?

How to make PHP Ticker Number Assignment More Robust?

我正在创建一个票务系统。我希望每个条目都是指定范围之间的唯一数字。我已经创建了函数,我将包含这些函数来展示我是如何处理事情的。如果我一口气买完所有的票,这个函数一般有3、4个重号。

我已经尝试过了,但如果一个人一次购买了所有门票,就会以一些重复的方式结束。我认为如果一次购买几个也会发生这种情况。这需要更强大。

已取票 returns 一组号码已取

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;
}

付款处理后,我调用此函数与随机票一起插入数据库。

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

我收到了重复的邮件。这在实时应用程序中不会发生。我已经尝试了很多事情,发现这对于编程来说是一个相当大的问题。任何和所有输入将不胜感激。提前谢谢大家。

您的数据库是这里的真实来源,它是所有可能的票号和已分配票的存储库。

当您尝试在 Web 应用程序中执行此操作时,如果有多个并发用户,或者当您的应用程序分布在多个服务器上时,总是有可能发生冲突。

也就是说,最好找到一个数据库解决方案来发行这些票。

您应该实施的第一件事是在您的交易 table 上执行 UNIQUE INDEX 记录票号,然后数据库将始终保持您的票号的完整性:

You have not provided the full schema so I am assuming that your transaction table has both a DrawID and a TicketNumber column

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

现在当您插入重复的票号时,数据库将无法操作。一个基本的开始方法是简单地处理失败并使用您当前的逻辑继续重试下一个数字,直到它起作用。

您可以使用 CTE 直接从数据库查询下一个票号:

WITH is supported in MySQL since version 8 (Released in 2016), if you are using an older version you could populate a concrete or temporary table with all the possible ticket numbers to use in place of this WITH clause


  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;

By default, MYSQL limits recursion to 1000, so you may need to set the current session recursion limit to a number larger than or equal to the totalTickets

SET SESSION cte_max_recursion_depth = 1000000;

现在您可以利用数据库的原子性来获取上述查询的结果并直接在您的插入语句中使用它,在 SQL 使用 CTE 的服务器中我会为事务做类似的事情更新:(我无法让它在 DB 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;博士;

为了确保原子性,想办法把这个过程移到数据库中,创建一个存储过程来封装分配票号的过程并将其与付款确认过程合并。