【问题标题】:How to generate unique random value for each user in laravel and add it to database如何在laravel中为每个用户生成唯一的随机值并将其添加到数据库中
【发布时间】:2015-04-15 22:51:27
【问题描述】:

我正在开发一个活动组织网站。在这里,当用户注册一个事件时,他将获得一个唯一的随机数(10 位),我们用它来生成一个条形码并将其邮寄给他。现在,

  1. 我想让每个注册事件的编号都是唯一的。
  2. 也是随机的

一种解决方案是获取数组中的所有随机数并使用 Php rand(1000000000, 9999999999) 生成一个随机数,然后循环并检查所有值。获取不等于数组中任何值的第一个值并将其添加到数据库中。

但我认为可能有更好的解决方案。有什么建议吗?

【问题讨论】:

  • “每个注册事件的唯一性”是否意味着事件 A 的随机数需要与事件 B 的随机数区分开来?
  • 我没有想到这种解释,但似乎合乎逻辑。如果是,我的答案很容易修改。

标签: php mysql laravel random eloquent


【解决方案1】:

一个解决方案可能是这样的:

use Illuminate\Support\Facades\Validator;
private function genUserCode(){
    $this->user_code = [
        'user_code' => mt_rand(1000000000,9999999999)
    ];

    $rules = ['user_code' => 'unique:users'];

    $validate = Validator::make($this->user_code, $rules)->passes();

    return $validate ? $this->user_code['user_code'] : $this->genUserCode();
}

它会生成一个介于 1000000000 和 9999999999 之间的随机数。之后,它会根据表格验证该数字。如果为真则返回数字,否则再次运行该函数。

【讨论】:

    【解决方案2】:

    您可以使用php的uniqid()函数根据微时间(当前时间,以微秒为单位)生成唯一ID

    例子:

    <?php
    echo uniqid();
    ?>
    

    输出:

    56c3096338cdb
    

    【讨论】:

    • 警告 此函数不保证返回值的唯一性。由于大多数系统通过 NTP 或类似的方式调整系统时钟,因此系统时间不断变化。因此,此函数可能不会返回进程/线程的唯一 ID。使用 more_entropy 增加唯一性的可能性。
    【解决方案3】:
    <?php
    declare(strict_types=1);
    
    namespace App\Helpers;
    
    
    use App\Exceptions\GeneratorException;
    
    class GeneratorHelper
    {
        public static $limitIterations = 100000;
    
        /**
         * @param string $column
         * @param string $modelClass
         * @return string
         * @throws GeneratorException
         */
        public static function generateID(string $modelClass, string $column): string
        {
            return self::run(
                $modelClass,
                $column,
                self::IDGenerator(),
                'Generation id is failed. The loop limit exceeds ' . self::$limitIterations
            );
        }
    
        /**
         * @param string     $modelClass
         * @param string     $column
         * @param \Generator $generator
         * @param string     $exceptionMessage
         * @param array      $whereParams
         * @return string
         * @throws GeneratorException
         */
        protected static function run(string $modelClass, string $column, \Generator $generator, string $exceptionMessage, array $whereParams = []): string
        {
            try {
                foreach ($generator as $id) {
                    $query = $modelClass::where([$column => $id]);
                    foreach ($whereParams as $param) {
                        $query->where(...$param);
                    }
                    if (!$query->first()) {
                        return $id;
                    }
                }
            } catch (\Throwable $e) {
                $exceptionMessage = $e->getMessage();
            }
    
            throw new GeneratorException($exceptionMessage);
        }
    
        protected static function IDGenerator(): ?\Generator
        {
            for ($i = 1; $i <= self::$limitIterations; $i++) {
                yield (string)random_int(1000000000, 9999999999);            
            }
            return null;
        }
    }
    

    示例用法

    $card->number = GeneratorHelper::generateID(Card::class, 'number');
    

    【讨论】:

    • 这是一个非常好的答案,应该被接受为答案。但是,GeneratorException.php 文件是什么样的?
    【解决方案4】:

    这很好:

    do {
       $refrence_id = mt_rand( 1000000000, 9999999999 );
    } while ( DB::table( 'transations' )->where( 'RefrenceID', $refrence_id )->exists() );
    

    【讨论】:

    • 通过在 WHILE 条件块中使用 count 来限制 do-while 块的循环/迭代次数,否则如果有大量条目且范围长度短,则可能需要很长时间/无限循环(4- 6 用于一次性密码)。
    【解决方案5】:

    为了避免每次创建新代码时都必须检查是否存在匹配代码的问题,我只是捕获了 MySQL 的重复记录异常(错误代码 1062)。如果发现该错误,我只需再次调用该函数,直到保存成功。这样,它只需要在与现有代码冲突时生成一个新代码。运行速度要快得多——但随着用户数量接近可能的条形码数量,显然会变慢一些。

    function generateBarcode($user_id) {
        try {
            $user = User::find($user_id);
            $user->barcode = mt_rand(1000000000, 9999999999);
            $user->save();
    
        } catch (Exception $e) {
            $error_info = $e->errorInfo;
            if($error_info[1] == 1062) {
                generateBarcode($user_id);
            } else {
                // Only logs when an error other than duplicate happens
                Log::error($e);
            }
    
        }
    }
    

    因此,只需遍历您要为其分配代码的所有用户:

    foreach(User::all() as $user) {
        generateBarcode($user->id);
    }
    

    如果进行了最大次数的尝试,您还可以添加一些逻辑来逃避函数循环,但我从不打扰,因为不太可能发生冲突。

    【讨论】:

      【解决方案6】:

      我做了这样的事情

      /**
       * Generate unique shipment ID
       * 
       * @param int $length
       * 
       * @return string
       */ 
      function generateShipmentId($length)
      {
          $number = '';
      
          do {
              for ($i=$length; $i--; $i>0) {
                  $number .= mt_rand(0,9);
              }
          } while ( !empty(DB::table('shipments')->where('id', $number)->first(['id'])) );
      
          return $number;
      }
      

      【讨论】:

        【解决方案7】:

        遍历数组不会那么有效。如果您的数据库变得太大,那么它会减慢整个过程,并且可能会出现一种罕见的情况,即 2 个线程在数组中循环以获取相同的随机数,并且会发现它可用并向两张票返回相同的数字。

        因此,您可以将 10 位注册 id 设置为主键,而不是遍历数组,而不是遍历数组,您可以插入注册详细信息以及随机生成的数字,如果数据库插入操作成功,您可以返回注册 id,如果没有,则重新生成随机数并插入。

        更有效的替代解决方案 您可以使用时间戳代替 10 位随机数来生成 10 位唯一注册号,并使其随机化,您可以随机化时间戳的前 2 或 3 位

        【讨论】:

        • 它甚至不必是主键。一个唯一的索引就足够了:)
        • @OP 您还在寻找其他解决方案吗?
        【解决方案8】:

        您的逻辑在技术上没有错误。但是,如果您的应用程序吸引了大量用户,那么在资源和计算时间方面,获取所有随机数可能会变得不必要地昂贵。

        我会建议另一种方法,您可以生成一个随机数,然后对照数据库进行检查。

        function generateBarcodeNumber() {
            $number = mt_rand(1000000000, 9999999999); // better than rand()
        
            // call the same function if the barcode exists already
            if (barcodeNumberExists($number)) {
                return generateBarcodeNumber();
            }
        
            // otherwise, it's valid and can be used
            return $number;
        }
        
        function barcodeNumberExists($number) {
            // query the database and return a boolean
            // for instance, it might look like this in Laravel
            return User::whereBarcodeNumber($number)->exists();
        }
        

        【讨论】:

        • with mt_rand(1000000000, 9999999999) 我只得到以 11...、10... 和 12... 开头的数字,这是一个奇怪的随机函数
        • @Joel Hinz 总是查询数据库的唯一性可能很麻烦。那么根据事务ID格式生成当前时间戳并附加一些其他字符串或随机数字是什么意思.. .:)
        • @SanjayKhadka 这是 1000000000、9999999999 的跨度......除非数据库包含数百万种产品,否则这不会成为问题。另外,我是按照 OP 的规范进行的。
        • 在 laravel 8 中我收到错误“未定义函数调用”。作为解决方案,调用如下函数。 if($this->barcodeNumberExists($number)) { }
        • @aseladaskon 它们只是函数。如果你把它们放在一个类中,你需要使用$this-&gt;来调用它们。
        猜你喜欢
        • 2020-02-06
        • 2011-09-06
        • 1970-01-01
        • 2018-06-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多