1

我快到了,但缺少一些东西。我的 PHP 应用程序:

1)用户正在向服务器请求

2)服务器正在生成一个长的唯一字符串并检查数据库中是否存在:如果是则再次生成(直到它不存在),如果否则将其添加到数据库并完成。所有逻辑都应通过单个请求执行,即如果生成的字符串存在,用户不应请求/刷新页面。

我被困在“是”部分。

我的代码(免责声明:我不拥有以下代码的一部分)

 <?php
    class genPass
    {
    private $db;
        function __construct() {
            $this->db=new mysqli('localhost', 'user', 'pass', 'db');
                $this->db->set_charset("utf8");
            $this->db->autocommit(FALSE);
        }
        function __destruct() {
            $this->db->close();
        }  

    function isUsed($uid)
    {
        $stmt=$this->db->query("SELECT * FROM id WHERE udid='".$uid."'")or die($this->db->error);

        while($stmt->num_rows <1) {
        $newnum = $this->generateStrongPassword();
        $newcheck=$this->db->query("SELECT * FROM id WHERE udid='".$newnum."'")or die($this->db->error);

        if ($newcheck->num_rows >= 1) {
            echo $newnum . " exists! \n";  <- WHAT TO DO IF EXISTS?WHICH PART OF THE SCRIPT SHOULD I RUN AGAIN
        } else {
            $this->db->query("INSERT INTO id (udid) VALUES ('".$newnum."')")or die($this->db->error);
            echo "$newnum - CAN ISNERT@!@!@";
            break;
        }
    }

    }
    public function generateStrongPassword($length = 3, $add_dashes = false, $available_sets = 'lu')
    {
        $sets = array();
        if(strpos($available_sets, 'l') !== false)
            $sets[] = 'ab';//'abcdefghjkmnpqrstuvwxyz';
        if(strpos($available_sets, 'u') !== false)
            $sets[] = 'AB';//'ABCDEFGHJKMNPQRSTUVWXYZ';
        if(strpos($available_sets, 'd') !== false)
            $sets[] = '23456789';
        if(strpos($available_sets, 's') !== false)
            $sets[] = '!@#$%&*?';

        $all = '';
        $password = '';
        foreach($sets as $set)
        {
            $password .= $set[array_rand(str_split($set))];
            $all .= $set;
        }

        $all = str_split($all);
        for($i = 0; $i < $length - count($sets); $i++)
            $password .= $all[array_rand($all)];

        $password = str_shuffle($password);

        if(!$add_dashes)
            return $password;

        $dash_len = floor(sqrt($length));
        $dash_str = '';
        while(strlen($password) > $dash_len)
        {
            $dash_str .= substr($password, 0, $dash_len) . '-';
            $password = substr($password, $dash_len);
        }
        $dash_str .= $password;
        return $this->$dash_str;
    }
    }


    $obj = new genPass;
    $ran=$obj->generateStrongPassword();
    $obj->isUsed($ran);

    ?>
4

2 回答 2

4

您正在使用一个函数isUsed(),这很好,但我建议将该函数限制为检查是否使用了随机密钥。

function isUsed($uid)
{
    $stmt=$this->db->query("SELECT * FROM id WHERE udid='".$uid."'")or die($this->db->error);
    if ($stmt->num_rows < 1) {
        return FALSE;
    } else {
        // Already a duplicate key, so should return TRUE for sure!!!
        return TRUE;
    }
}

这样,您可以使用循环来检查:

while $obj->isUsed($ran) {
    $ran = $obj->generateStrongPassword();
}
// put pwd in database after this loop!

顺便说一句,这只是为了解释必须使用的逻辑......检查你的代码是否有进一步的不一致...... :-)

于 2013-10-06T18:37:33.887 回答
1

好吧,我咬一口:

class GenPass
{
    private $isUsedStmt = null;
    private $db = null;
    //constructor etc...
    /**
     * Works like most hashtables: re-hashes $id param until a unique hash is found
     * uses sha256 algo, currently, no hash-collisions have been found, so pretty solid
     * you could change to sha512, but that would be overkill
     * @return string
     **/
    public function insertUniqueRandom($id = null)
    {
        $id = $id ? $id : $this->getRandomId();
        do
        {//use hashes, rehash in case of collision
            $id = hash('256', $id);
        }while($this->isUsed($id));
        //once here, current $id hash has no collisions
        $this->db->query('INSERT INTO `id` (udid) VALUES ("'.$id.'")');
        return $id;//returns unique has that has been found & used/storred
    }
    /**
     * Random string generator... returns random string
     * of specfied $length (default 10)
     * @param int $length = 10
     * @return String
     **/
    public function getRandomId($length = 10)
    {
        $length = (int) ($length > 1 ? $length : 10);
        $src = '0`12345~6789abc2%def&gh$ijklm!nopq?,rs><tuvwxyz';
        $out ='';
        for ($p = 0; $p < $length; $p++)
        {
            $char = $src{mt_rand(0, strlen($src))};
            $out .= $p%2 ? strtoupper($char) : $char;
        }
        return $out;
    }
    /**
     * Check if current hash already exists in DB, if so, return false, if not, return true
     * @return Boolean
     * @throws RuntimeException
     **/
    private function isUsed($uid)
    {
        $stmt = $this->getCheckUidStmt();
        $stmt->bindParam('s', $uid);
        if ($stmt->execute)
        {
            return $stmt->num_rows === 0 ? false : true;
        }
        throw new RuntimeException('failed to query for uid usage: '.$this->db->error);
    }
    /**
     * Lazy-load isUsed's prepared statement
     * The statement won't be prepared, unless the isUsed function is called
     * @return \mysqli::prepare
     **/
    private function getCheckUidStmt()
    {
        if ($this->isUsedStmt === null)
        {
            $this->isUsedStmt = $this->db->prepare('SELECT udid FROM `id` WHERE udid = ?');
        }
        return $this->isUsedStmt;
    }
}

正如评论所说,这就是大多数哈希表的工作方式:散列一个随机值,如果该散列已被使用,只需再次散列重复的散列,直到该散列没有在任何地方使用。
用法:

$gen = new GenPass;
$usedID = $gen->insertUniqueRandom();
echo $usedID, ' was just inserted';
$another = $gen->insertUniqueRandom('foobar');//works, 
echo $another;//will echo:
//c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2
$foobarAgain = $gen->insertUniqueRandom('foobar');
echo $foobarAgain;//foobar already existed, now this will echo:
//181cd581758421220b8c53d143563a027b476601f1a837ec85ee6f08c2a82cad

如您所见,尝试插入“foobar”两次将导致 2 个唯一 ID。更重要的是,sha256 散列的长度是给定的:它的 256 位长,或 64 个字符,因此可以很容易地存储在数据库中:VARCHAR(64)这就是您所需要的……简单!
考虑到所有因素,我认为公平地说,这可能是您将获得的最接近可靠、相当快的唯一随机 id 生成器的方法

于 2013-10-06T19:06:09.090 回答