我差点儿,但缺少一些东西。我的PHP应用程序:
1)用户正在请求服务器
2)服务器正在生成一个长唯一字符串,并检查数据库中是否存在:如果是,则再次生成(直到它不存在),如果否,则将其添加到数据库并完成。所有逻辑都应该用单个请求执行,即如果生成的字符串存在,用户不应该请求/刷新页面。
我被困在YES部分。
我的代码(免责声明:我不拥有以下代码的部分内容)
<?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);
?>
答案 0 :(得分: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!
顺便说一句,这只是为了解释必须使用的逻辑...检查代码是否存在进一步的不一致......: - )
答案 1 :(得分: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生成器