如何在laravel中为每个用户生成唯一的随机值并将其添加到数据库中


How to generate unique random value for each user in laravel and add it to database

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

  1. 我想让每个注册活动的号码都是唯一的
  2. 也是随机的

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

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

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

示例:

<?php
echo uniqid();
?>

输出:

56c3096338cdb

您的逻辑在技术上没有故障。然而,如果您的应用程序吸引了大量用户,那么从资源和计算时间来看,获取所有随机数可能会变得不必要地昂贵。

我建议另一种方法,生成一个随机数,然后将其与数据库进行核对。

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

这很好:

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

为了避免每次创建新代码时都要检查是否存在匹配代码的问题,我只捕获了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);
}

如果尝试次数达到最大值,您也可以添加一些逻辑来逃离函数循环,但我从未考虑过,因为不太可能发生冲突。

在数组中循环不会那么有效。如果你的数据库变得太大,那么它会减慢整个过程,而且可能会出现罕见的情况,即2个线程在数组中循环使用相同的随机数,并且它会被发现可用,并向两个票证返回相同的数字。

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

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

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

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之间的随机数。然后,它根据表验证数字。如果为true,则返回数字,否则再次运行函数。

我做了这样的

/**
 * 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;
}
<?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');

Helper(app/Helpers/Constants.php)

<?php
namespace App'Helper;
class Constants
{
    public static function getUniqueId($model){
        $id =  mt_rand(1000000000, 9999999999);
        if($model->find($id))
            return self::getUniqueId($model);
        return $id;
    }
}
?>

模型(app/Models/User.php)

<?php
namespace App;
use Illuminate'Database'Eloquent'Model;
class User extends Model
{
    public static function boot(){
        parent::boot();
        $creationCallback = function ($model) {
            if (empty($model->{$model->getKeyName()}))
                $model->{$model->getKeyName()} = Constants::getUniqueId(new self());
        };
        static::creating($creationCallback);
    }
}
?>

解释:而不是在每个控制器中调用getUniqueId方法。你可以把它写在模型里。

来自@Joel Hinz的回答:

public function set_number() {
    $number = mt_rand(1000000000, 9999999999);
    return User::where('number', $number)->exists() ? $this->set_number() : $number;
}

希望能有所帮助。

对我来说,我更喜欢使用MySQL的方式,因为当你的数据库中有大量数据时,你会有太多的quire来检查你的数字唯一性,例如,像这样的代码:

 do {
     $code = random_int(100000, 99999999);
    } 
while (AgentOrder::where("order_number", "=", $code)->exists());

所以,这个";CCD_ 2";循环会被省略太多次。

为了避免这种情况,您可以使用MySQL的方式,如:

  private function getUniqueCodeNumber()
    {
        $value = AgentOrder::query()->selectRaw('FLOOR(1000000 + RAND() * 10000000) AS generatedCode')
            ->whereRaw("'generatedCode'  NOT IN (SELECT order_number FROM agent_orders WHERE agent_orders.order_number IS NOT NULL)")
            ->limit(1)->first();
        if ($value == null) return 100000000;
        $value = (int)$value->generatedCode;
        return $value;
    }

这个答案的灵感来源于这个答案。