短链生成demo
发表于 2023-11-10 | | 开发笔记

1.我们可以对原始 URL 进行哈希得到一个数字,然后将这个数字进一步转换为包含字母和数字的 8 位字符串,作为短链接中的唯一标识符。这样,即使原始 URL 非常长,我们也能够生成一个较短且易于记忆的短链接。

    //简易版
    class ShortLinkGenerator {
      // 计算字符串的哈希值
      private static function hashString($str) {
        $hash = 0x811c9dc5;
        for ($i = 0; $i < strlen($str); $i++) {
          $hash ^= ord($str[$i]);
          $hash *= 0x1000193;
          $hash &= 0xffffffff;
        }
        return $hash;
      }

      // 数字转换为短链接字符串
      private static function numberToShortLink($num) {
        $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
        $string = '';
        $maxIndex = strlen($characters) - 1;
        for ($i = 0; $i < 8; $i++) {
          $string .= $characters[$num % ($maxIndex + 1)];
          $num = intval($num / ($maxIndex + 1));
        }
        return $string;
      }

      // 根据原始 URL 生成短链接
      public static function generate($url) {
        $hash = self::hashString($url);
        $shortKey = self::numberToShortLink($hash);
        return "https://example.com/{$shortKey}";
      }
    }

    // 示例用法
    $originalUrl = 'https://www.example.com/article/123';
    $shortLink = ShortLinkGenerator::generate($originalUrl);
    echo "原始 URL: {$originalUrl}\n";
    echo "短链接: {$shortLink}\n";

在这里,我们对 generate 函数进行了修改。

  • 现在生成的短链接只包含域名后的 8 位字符串。我们将计算出的哈希值传递给 numberToShortLink 函数,该函数会将哈希值转换为一个 8 位的字符串。
  • 最后,我们将该字符串插入到域名后面,得到一个完整的短链接。需要注意的是,由于使用了较短的字符串作为短链接标识符,因此可能存在冲突的风险。在实际应用中,最好采取一些措施来避免重复的短链接,比如增加长度或者使用更强的哈希算法等。
  • 在这里,我们对 generate 函数进行了修改。现在生成的短链接只包含域名后的 8 位字符串。我们将计算出的哈希值传递给 numberToShortLink 函数,该函数会将哈希值转换为一个 8 位的字符串。
  • 最后,我们将该字符串插入到域名后面,得到一个完整的短链接。需要注意的是,由于使用了较短的字符串作为短链接标识符,因此可能存在冲突的风险。
  • 在实际应用中,最好采取一些措施来避免重复的短链接,比如增加长度或者使用更强的哈希算法等。

2.对短链接添加生命周期、重复检查等功能

针对短链接的生命周期、重复检查等功能,我们可以考虑以下实现方式:

  • 短链接的生命周期:短链接可以设置一个有效期,超过这个时间后就会失效。可以在数据库中添加一个字段来存储链接的创建时间,并根据需要进行查询和更新。当用户使用失效的短链接时,系统会返回一个提示页面,告知用户该链接已经过期。
  • 短链接的重复检查:为了避免生成重复的短链接,可以在数据库中添加一个唯一索引,用于存储短链接的标识符。在生成新的短链接时,先查询数据库是否已经存在相同的标识符,如果存在,则重新生成。可以在生成短链接的 generate 函数中添加一个循环,不断尝试新的标识符,直到找到一个未被占用的标识符为止。如果重复检查的逻辑比较复杂,也可以专门编写一个函数来完成检查。
  • 数据库设计:为了存储短链接的信息,我们需要设计一个数据库表。可以考虑包含以下字段:
    id: 主键,自增长。
    original_url: 原始链接。
    short_key: 短链接的标识符。
    create_time: 链接的创建时间。
    expire_time: 链接的过期时间。
  • 这些字段可以根据具体业务需求进行调整。为了提高查询效率,可以在 short_key 字段上添加一个唯一索引。 这些字段可以根据具体业务需求进行调整。为了提高查询效率,可以在 short_key 字段上添加一个唯一索引。
    
    class ShortLinkGenerator {
    // 计算字符串的哈希值
    private static function hashString($str) {
    $hash = 0x811c9dc5;
    for ($i = 0; $i < strlen($str); $i++) {
    $hash ^= ord($str[$i]);
    $hash *= 0x1000193;
    $hash &= 0xffffffff;
    }
    return $hash;
    }
  // 数字转换为短链接字符串
  private static function numberToShortLink($num) {
    $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $string = '';
    $maxIndex = strlen($characters) - 1;
    for ($i = 0; $i < 8; $i++) {
      $string .= $characters[$num % ($maxIndex + 1)];
      $num = intval($num / ($maxIndex + 1));
    }
    return $string;
  }

  // 生成唯一的短链接标识符
  private static function generateUniqueShortKey($db) {
    $isUnique = false;
    while (!$isUnique) {
      // 生成新的标识符
      $hash = mt_rand();
      $shortKey = self::numberToShortLink($hash);

      // 检查是否已经存在相同的标识符
      $stmt = $db->prepare('SELECT COUNT(*) FROM short_links WHERE short_key = ?');
      $stmt->bindParam(1, $shortKey);
      $stmt->execute();
      $count = $stmt->fetchColumn();

      if ($count == 0) {
        $isUnique = true;
      }
    }
    return $shortKey;
  }

  // 根据原始 URL 生成短链接
  public static function generate($db, $url) {
    // 生成唯一的短链接标识符
    $shortKey = self::generateUniqueShortKey($db);

    // 添加短链接到数据库
    $stmt = $db->prepare('INSERT INTO short_links (original_url, short_key, create_time, expire_time) VALUES (?, ?, NOW(), DATE_ADD(NOW(), INTERVAL 1 DAY))');
    $stmt->bindParam(1, $url);
    $stmt->bindParam(2, $shortKey);
    $stmt->execute();

    // 返回完整的短链接
    return "https://example.com/{$shortKey}";
  }

  // 根据短链接查询原始 URL
  public static function getOriginalUrl($db, $shortKey) {
    $stmt = $db->prepare('SELECT original_url FROM short_links WHERE short_key = ? AND expire_time > NOW()');
    $stmt->bindParam(1, $shortKey);
    $stmt->execute();
    $row = $stmt->fetch(PDO::FETCH_ASSOC);
    if ($row) {
      return $row['original_url'];
    } else {
      return null;
    }
  }
}

// 示例用法
$db = new PDO('mysql:host=localhost;dbname=test', 'user', 'password');

// 生成新的短链接
$originalUrl = 'https://www.example.com/article/123';
$shortLink = ShortLinkGenerator::generate($db, $originalUrl);
echo "原始 URL: {$originalUrl}\n";
echo "短链接: {$shortLink}\n";

// 根据短链接查找原始 URL
$shortKey = substr($shortLink, strlen('https://example.com/'));
$originalUrl = ShortLinkGenerator::getOriginalUrl($db, $shortKey);
if ($originalUrl) {
  echo "原始 URL: {$originalUrl}\n";
} else {
  echo "短链接无效\n";
}

>在这里,我们添加了一个名为 short_links 的表来存储短链接的信息。在生成新的短链接时,先调用 generateUniqueShortKey 函数生成一个唯一的标识符,然后将其和原始 URL 一起添加到数据库中。在查询原始 URL 时,需要检查短链接是否已经过期,如果已经过期则返回空。以上仅作为示例,实际应用中还需要考虑一些其他因素,比如并发访问、安全性、错误处理等。

****3.对以上代码添加并发访问、安全性、错误处理
>  以下是对之前代码进行并发访问、安全性、错误处理等方面的改进:
>- 并发访问处理:为了避免并发访问导致的数据库写入冲突,可以添加一个写锁来保护 short_links 表。在添加新的短链接时获取锁,释放锁后才能进行下一个添加操作。可以使用 MySQL 的 GET_LOCK() 和 RELEASE_LOCK() 函数实现锁机制。
>- 安全性处理:需要对原始 URL 进行校验,防止恶意用户利用生成的短链接访问危险网站或执行跨站脚本攻击。可以使用 PHP 的 filter_var() 函数来检验 URL 是否合法、是否为 HTTP 或 HTTPS 协议,以及是否包含非法字符等。
>- 错误处理:需要在代码中添加错误处理机制,确保程序在出现异常情况时能够正常运行并给出有意义的错误提示。可以使用 PHP 的 try...catch 语句来捕获和处理异常。

```php
    class ShortLinkGenerator {
      // 计算字符串的哈希值
      private static function hashString($str) {
        $hash = 0x811c9dc5;
        for ($i = 0; $i < strlen($str); $i++) {
          $hash ^= ord($str[$i]);
          $hash *= 0x1000193;
          $hash &= 0xffffffff;
        }
        return $hash;
      }

      // 数字转换为短链接字符串
      private static function numberToShortLink($num) {
        $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
        $string = '';
        $maxIndex = strlen($characters) - 1;
        for ($i = 0; $i < 8; $i++) {
          $string .= $characters[$num % ($maxIndex + 1)];
          $num = intval($num / ($maxIndex + 1));
        }
        return $string;
      }

      // 获取短链接锁
      private static function getShortLinkLock($db) {
        $stmt = $db->prepare('SELECT GET_LOCK("short_link_lock", 10)');
        $stmt->execute();
        $isLocked = $stmt->fetchColumn();
        if (!$isLocked) {
          throw new Exception("获取短链接锁失败");
        }
      }

      // 释放短链接锁
      private static function releaseShortLinkLock($db) {
        $stmt = $db->prepare('SELECT RELEASE_LOCK("short_link_lock")');
        $stmt->execute();
      }

      // 生成唯一的短链接标识符
      private static function generateUniqueShortKey($db) {
        $isUnique = false;
        while (!$isUnique) {
          // 生成新的标识符
          $hash = mt_rand();
          $shortKey = self::numberToShortLink($hash);

          // 检查是否已经存在相同的标识符
          $stmt = $db->prepare('SELECT COUNT(*) FROM short_links WHERE short_key = ?');
          $stmt->bindParam(1, $shortKey);
          $stmt->execute();
          $count = $stmt->fetchColumn();

          if ($count == 0) {
            $isUnique = true;
          }
        }
        return $shortKey;
      }

      // 根据原始 URL 生成短链接
      public static function generate($db, $url) {
        try {
          // 检查 URL 是否合法
          $url = filter_var($url, FILTER_SANITIZE_URL);
          if (!$url || !preg_match('/^https?:\/\//', $url)) {
            throw new Exception("非法的 URL");
          }

          // 获取短链接锁
          self::getShortLinkLock($db);

          // 生成唯一的短链接标识符
          $shortKey = self::generateUniqueShortKey($db);

          // 添加短链接到数据库
          $stmt = $db->prepare('INSERT INTO short_links (original_url, short_key, create_time, expire_time) VALUES (?, ?, NOW(), DATE_ADD(NOW(), INTERVAL 1 DAY))');
          $stmt->bindParam(1, $url);
          $stmt->bindParam(2, $shortKey);
          $stmt->execute();

          // 释放短链接锁
          self::releaseShortLinkLock($db);

          // 返回完整的短链接
          return "https://example.com/{$shortKey}";
        } catch (Exception $e) {
          // 处理异常情况
          self::releaseShortLinkLock($db);
          throw new Exception($e->getMessage());
        }
      }

      // 根据短链接查询原始 URL
      public static function getOriginalUrl($db, $shortKey) {
        try {
          // 检查短链接是否合法
          if (!preg_match('/^[0-9a-zA-Z]{8}$/', $shortKey)) {
            throw new Exception("非法的短链接");
          }

          $stmt = $db->prepare('SELECT original_url FROM short_links WHERE short_key = ? AND expire_time > NOW()');
          $stmt->bindParam(1, $shortKey);
          $stmt->execute();
          $row = $stmt->fetch(PDO::FETCH_ASSOC);
          if ($row) {
            return $row['original_url'];
          } else {
            throw new Exception("短链接无效");
          }
        } catch (Exception $e) {
          // 处理异常情况
          throw new Exception($e->getMessage());
        }
      }
    }

    // 示例用法
    $db = new PDO('mysql:host=localhost;dbname=test', 'user', 'password');

    // 生成新的短链接
    $originalUrl = 'https://www.example.com/article/123';
    try {
      $shortLink = ShortLinkGenerator::generate($db, $originalUrl);
      echo "原始 URL: {$originalUrl}\n";
      echo "短链接: {$shortLink}\n";
    } catch (Exception $e) {
      echo "生成短链接失败: {$e->getMessage()}\n";
    }

    // 根据短链接查找原始 URL
    $shortKey = substr($shortLink, strlen('https://example.com/'));
    try {
      $originalUrl = ShortLinkGenerator::getOriginalUrl($db, $shortKey);
      echo "原始 URL: {$originalUrl}\n";
    } catch (Exception $e) {
      echo "获取原始 URL 失败: {$e->getMessage()}\n";
    }

在这里,我们添加了获取和释放锁的方法,并在 generate 函数中调用。在异常情况下,需要使用 try...catch 语句来正确处理异常并释放锁。同时,我们也对输入的 URL 和短链接进行了校验,在非法情况下抛出异常

发表评论:

TOP