2

我目前正在开展一个项目,其中对 API 的访问仅限于注册用户。API 本身已经完成并按预期工作。限制对 API 的访问也变得相当简单。但是,我的问题(或者更确切地说是问题)是如何确保注册、验证和/或失物招领过程的数据库交互效率。

这是当前发生的情况的示例:

  1. 用户通过输入他们的电子邮件地址请求 API 密钥
  2. 用户收到一封验证电子邮件
  3. 用户单击电子邮件中的链接,php 检查数据库的哈希值
  4. 验证哈希后,生成、存储 API 密钥并通过电子邮件发送
  5. 如果用户忘记/丢失 API 密钥,可以再次通过电子邮件发送
  6. 如果没有收到验证邮件,可以再次发送邮件

这是数据库结构的示例: http: //s13.postimage.org/h8ao5oo2v/dbstructure.png

正如您可能想象的那样,对于流程中的每个特定步骤,幕后都在进行大量的数据库交互。我想知道效率的一个步骤是检查某些项目的唯一性。显然,我们不希望出现任何重复的 API 密钥,也不希望出现任何重复的电子邮件验证哈希。

所以,我写了一个简单的函数,在将它们插入数据库之前检查数据库中的这些东西。然而,这个项目比我以前做过的任何项目都要大数百倍。我之前构建并维护过为 500 到 1,000 名用户提供服务的项目……但据估计,该项目每天至少为大约 50,000 名用户提供服务。我非常高兴我终于完成了一个大型项目,但对它的规模越来越感到畏惧。

无论如何,这是我编写的用于与数据库交互以在存储项目之前检查项目的唯一性的函数。

function isUnique($table, $col, $data) {
  mysql_connect("localhost", "root", "") or die(mysql_error());  
  mysql_select_db("api") or die(mysql_error());
  $check = mysql_query("SELECT ".$col." FROM ".$table." WHERE ".$col."='".$data."'");
  $match = mysql_num_rows($check);
  if($match < 1) {
    return true;
  }
  return false;
  mysql_close('localhost');
}

此函数与另一个函数结合使用,该函数仅生成一个随机的 40 位 0-9、az 和 AZ 字符串,用于电子邮件验证哈希以及 API 密钥本身。(功能如下)

function makeRandom($length = 40) {
  $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
  $randomString = '';
  for($i = 0; $i < $length; $i++) {
    $randomString .= $characters[mt_rand(0, strlen($characters) - 1)];
  }
  return $randomString;
}

然后将这两个功能的组合用于与 API 密钥颁发相关的 3 个不同页面:第一页用于注册/请求,第二页用于验证电子邮件,第三页用于丢失密钥或未收到的电子邮件。现在在实践中:

$hash   = makeRandom();
$unique = isUnique('users', 'hash', $hash);
if($unique == false) {
  while($unique == false) {
    $hash   = makeRandom();
    $unique = isUnique('users', 'hash', $hash);
  }
}
else {
  $searchactive   = mysql_query("SELECT email, active FROM users WHERE email='".$email."' AND active='1'") or die(mysql_error());
  $matchactive    = mysql_num_rows($searchactive);
  $searchinactive = mysql_query("SELECT email, active FROM users WHERE email='".$email."' AND active='0'") or die(mysql_error());
  $matchinactive  = mysql_num_rows($searchinactive);

  if($matchactive > 0) {
    $hash = mysql_query("SELECT hash FROM users WHERE email='".$email."' AND active='1'") or die(mysql_error());
    $hash = mysql_fetch_assoc($hash);
    $hash = $hash['hash'];
    $msg = 'The email address you entered is already associated with an active API key. <a href="lost.php?email='.$email.'&amp;hash='.$hash.'&active=1">[Recover Lost API Key]</a>';
  }
  elseif($matchinactive > 0) {
    $hash = mysql_query("SELECT hash FROM users WHERE email='".$email."' AND active='0'") or die(mysql_error());
    $hash = mysql_fetch_assoc($hash);
    $hash = $hash['hash'];
    $msg = 'The email address you entered is already pending verification. <a href="lost.php?email='.$email.'&amp;hash='.$hash.'&active=0">[Resend Verification Email]</a>';
  }
}

我的主要问题是:对于这样一个(看似)简单的功能进行这么多的查询,这会产生比它解决的问题更多的问题吗?出于明显的原因,我确实需要确保没有任何重复的验证哈希或 API 密钥。但是,估计有 50k 人在使用此功能,这是否会因为 SQL 查询的数量而使服务器陷入困境?主要问题是由于 while() 循环用于在插入之前检查生成的内容的唯一性。

我知道这不是幕后发生的事情的完整画面,但它确实为其余页面的工作方式提供了线索。如果需要有关整个过程的更多信息,我很乐意发布。

感谢您提供的任何见解!

4

2 回答 2

2

解决此问题的一种方法是不检查重复项,而只是确保它们从一开始就不会发生。因此,只需对您的用户表进行版本化(为版本添加一个字段)。这将只是一个在用户行更改时前进的 int。

然后,当您生成随机密钥时,在存储密钥之前将 user_id 和 user_version 添加到其中。

例子:

11ap0w9jfoaiwej203989wesef

其中第一个 1 是 user_id,第二个 1 是用户版本。

然后,即使在统计上很小的机会生成两次大密钥,它也将始终是唯一的,因为您的用户 ID 将是唯一的。

于 2012-05-03T20:20:05.610 回答
1

我会考虑使用 UUID 而不是滚动您自己的随机字符串。出于所有实际目的,这将是一个独特的价值。

http://dev.mysql.com/doc/refman/5.5/en/miscellaneous-functions.html#function_uuid

于 2012-05-03T20:28:54.333 回答