PHP项目短链接如何生成跳转解析

wen PHP项目 25

本文目录导读:

PHP项目短链接如何生成跳转解析

  1. 数据库表结构设计
  2. 短链接生成核心代码
  3. URL跳转处理(路由)
  4. API 接口实现
  5. 安全优化建议
  6. 使用示例

我来详细讲解PHP项目中的短链接生成和跳转解析的实现方案:

数据库表结构设计

CREATE TABLE `short_links` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT,
    `short_code` varchar(10) NOT NULL COMMENT '短链接码',
    `original_url` text NOT NULL COMMENT '原始URL',
    `expire_time` datetime DEFAULT NULL COMMENT '过期时间',
    `click_count` int(11) DEFAULT '0' COMMENT '点击次数',
    `status` tinyint(4) DEFAULT '1' COMMENT '状态:1启用 0禁用',
    `created_at` datetime DEFAULT CURRENT_TIMESTAMP,
    `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_short_code` (`short_code`),
    KEY `idx_original_url` (`original_url`(255)),
    KEY `idx_expire_time` (`expire_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

短链接生成核心代码

1 字符编码生成器

<?php
class ShortLinkGenerator {
    // 可使用的字符集
    private static $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    /**
     * 生成短码(基于雪花算法+进制转换)
     */
    public static function generate()
    {
        $id = self::getUniqueId();
        return self::base62Encode($id);
    }
    /**
     * 获取唯一ID(使用雪花算法或自增ID)
     */
    private static function getUniqueId()
    {
        // 使用时间戳+随机数生成唯一ID
        $time = microtime(true) * 1000;
        $random = mt_rand(100000, 999999);
        return $time . $random;
    }
    /**
     * 10进制转62进制
     */
    private static function base62Encode($num)
    {
        $base = strlen(self::$characters);
        $code = '';
        while ($num > 0) {
            $remainder = $num % $base;
            $code = self::$characters[$remainder] . $code;
            $num = floor($num / $base);
        }
        return $code ?: '0';
    }
    /**
     * 62进制转10进制
     */
    public static function base62Decode($code)
    {
        $base = strlen(self::$characters);
        $len = strlen($code);
        $num = 0;
        for ($i = 0; $i < $len; $i++) {
            $pos = strpos(self::$characters, $code[$i]);
            $num += $pos * pow($base, $len - $i - 1);
        }
        return $num;
    }
    /**
     * 检查短码是否已存在
     */
    public static function checkUnique($code, $pdo)
    {
        $stmt = $pdo->prepare("SELECT id FROM short_links WHERE short_code = ?");
        $stmt->execute([$code]);
        return !$stmt->fetch();
    }
}

2 短链接服务类

<?php
class ShortLinkService {
    private $pdo;
    public function __construct($pdo)
    {
        $this->pdo = $pdo;
    }
    /**
     * 创建短链接
     */
    public function createShortLink($originalUrl, $expireTime = null)
    {
        // 验证URL
        if (!filter_var($originalUrl, FILTER_VALIDATE_URL)) {
            throw new Exception('无效的URL');
        }
        // 生成唯一短码
        $maxAttempts = 10;
        $shortCode = '';
        for ($i = 0; $i < $maxAttempts; $i++) {
            $code = ShortLinkGenerator::generate();
            $code = substr($code, 0, 8); // 取前8位
            if (ShortLinkGenerator::checkUnique($code, $this->pdo)) {
                $shortCode = $code;
                break;
            }
        }
        if (empty($shortCode)) {
            throw new Exception('生成短链接失败,请重试');
        }
        // 保存到数据库
        $stmt = $this->pdo->prepare("
            INSERT INTO short_links (short_code, original_url, expire_time, status, created_at) 
            VALUES (?, ?, ?, 1, NOW())
        ");
        $stmt->execute([
            $shortCode,
            $originalUrl,
            $expireTime
        ]);
        // 返回完整短链接
        return [
            'short_code' => $shortCode,
            'short_url' => $this->getFullShortUrl($shortCode),
            'original_url' => $originalUrl
        ];
    }
    /**
     * 获取完整短链接
     */
    private function getFullShortUrl($shortCode)
    {
        $domain = 'https://s.yourdomain.com'; // 替换为你的短域名
        return $domain . '/' . $shortCode;
    }
    /**
     * 根据短码获取原始URL
     */
    public function getOriginalUrl($shortCode)
    {
        $stmt = $this->pdo->prepare("
            SELECT original_url, expire_time, status, click_count 
            FROM short_links 
            WHERE short_code = ? 
            LIMIT 1
        ");
        $stmt->execute([$shortCode]);
        $link = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$link) {
            return null;
        }
        // 检查状态
        if ($link['status'] == 0) {
            throw new Exception('链接已被禁用');
        }
        // 检查过期
        if ($link['expire_time'] && strtotime($link['expire_time']) < time()) {
            throw new Exception('链接已过期');
        }
        // 更新点击次数
        $this->updateClickCount($shortCode);
        return $link['original_url'];
    }
    /**
     * 更新点击次数
     */
    private function updateClickCount($shortCode)
    {
        $stmt = $this->pdo->prepare("
            UPDATE short_links 
            SET click_count = click_count + 1 
            WHERE short_code = ?
        ");
        $stmt->execute([$shortCode]);
    }
    /**
     * 删除短链接
     */
    public function deleteShortLink($shortCode)
    {
        $stmt = $this->pdo->prepare("
            UPDATE short_links SET status = 0 
            WHERE short_code = ?
        ");
        return $stmt->execute([$shortCode]);
    }
    /**
     * 获取链接统计
     */
    public function getLinkStats($shortCode)
    {
        $stmt = $this->pdo->prepare("
            SELECT * FROM short_links 
            WHERE short_code = ?
        ");
        $stmt->execute([$shortCode]);
        return $stmt->fetch(PDO::FETCH_ASSOC);
    }
}

URL跳转处理(路由)

1 Apache .htaccess 配置

<IfModule mod_rewrite.c>
    RewriteEngine On
    # 排除已有文件
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    # 将所有短链接请求路由到 redirect.php
    RewriteRule ^([a-zA-Z0-9]+)$ redirect.php?code=$1 [L,QSA]
</IfModule>

2 Nginx 配置

server {
    listen 80;
    server_name s.yourdomain.com;
    location / {
        if (!-e $request_filename) {
            rewrite ^/([a-zA-Z0-9]+)$ /redirect.php?code=$1 last;
        }
    }
    location ~ \.php$ {
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

3 redirect.php 跳转处理

<?php
// redirect.php
require_once 'config.php';  // 数据库配置
require_once 'ShortLinkService.php';
require_once 'ShortLinkGenerator.php';
try {
    // 获取短码
    $shortCode = isset($_GET['code']) ? trim($_GET['code']) : '';
    if (empty($shortCode)) {
        throw new Exception('无效的短链接');
    }
    // 数据库连接
    $pdo = new PDO('mysql:host=localhost;dbname=short_links', 'username', 'password');
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    // 创建服务实例
    $service = new ShortLinkService($pdo);
    // 获取原始URL
    $originalUrl = $service->getOriginalUrl($shortCode);
    if ($originalUrl) {
        // 记录访问日志(可选)
        logAccess($shortCode);
        // 302跳转到原始URL
        header("HTTP/1.1 302 Found");
        header("Location: " . $originalUrl);
        exit;
    } else {
        throw new Exception('短链接不存在');
    }
} catch (Exception $e) {
    // 错误处理
    header("HTTP/1.1 404 Not Found");
    echo json_encode([
        'error' => true,
        'message' => $e->getMessage()
    ]);
}
/**
 * 记录访问日志
 */
function logAccess($shortCode)
{
    $logData = [
        'time' => date('Y-m-d H:i:s'),
        'ip' => $_SERVER['REMOTE_ADDR'],
        'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
        'referer' => $_SERVER['HTTP_REFERER'] ?? '',
        'code' => $shortCode
    ];
    // 写入日志文件
    $logFile = __DIR__ . '/logs/access.log';
    file_put_contents($logFile, json_encode($logData) . "\n", FILE_APPEND);
}

API 接口实现

1 创建短链接API

<?php
// api/create.php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    http_response_code(405);
    echo json_encode(['error' => '不允许的请求方法']);
    exit;
}
$input = json_decode(file_get_contents('php://input'), true);
if (!isset($input['url'])) {
    http_response_code(400);
    echo json_encode(['error' => '请提供URL']);
    exit;
}
try {
    require_once '../config.php';
    require_once '../ShortLinkService.php';
    require_once '../ShortLinkGenerator.php';
    $pdo = new PDO($dsn, $username, $password);
    $service = new ShortLinkService($pdo);
    $expireTime = isset($input['expire_time']) ? $input['expire_time'] : null;
    $result = $service->createShortLink($input['url'], $expireTime);
    http_response_code(200);
    echo json_encode([
        'success' => true,
        'data' => $result
    ]);
} catch (Exception $e) {
    http_response_code(500);
    echo json_encode([
        'success' => false,
        'error' => $e->getMessage()
    ]);
}

安全优化建议

1 防滥用措施

<?php
class SecurityMiddleware {
    /**
     * 限制IP请求频率
     */
    public static function rateLimit($ip, $limit = 100, $timeWindow = 3600)
    {
        $cacheFile = __DIR__ . '/cache/rate_' . md5($ip) . '.json';
        $data = [];
        if (file_exists($cacheFile)) {
            $data = json_decode(file_get_contents($cacheFile), true);
        }
        // 清理过期记录
        $now = time();
        $data = array_filter($data, function($time) use ($now, $timeWindow) {
            return ($now - $time) < $timeWindow;
        });
        if (count($data) >= $limit) {
            throw new Exception('请求过于频繁,请稍后再试');
        }
        $data[] = $now;
        file_put_contents($cacheFile, json_encode($data));
    }
    /**
     * URL安全过滤
     */
    public static function sanitizeUrl($url)
    {
        // 防止SSRF攻击
        $forbidden = [
            'localhost',
            '127.0.0.1',
            '192.168.',
            '10.',
            '172.16.',
            '0.0.0.0'
        ];
        $urlParts = parse_url($url);
        $host = $urlParts['host'] ?? '';
        foreach ($forbidden as $pattern) {
            if (strpos($host, $pattern) !== false) {
                throw new Exception('不允许的URL地址');
            }
        }
        // 编码验证
        if (!filter_var($url, FILTER_VALIDATE_URL)) {
            throw new Exception('无效的URL格式');
        }
        return $url;
    }
}

2 防碰撞检查

<?php
class CollisionChecker {
    /**
     * 使用布隆过滤器检查短码是否存在
     */
    public static function checkWithBloomFilter($code)
    {
        // 使用Redis实现布隆过滤器
        $redis = new Redis();
        $redis->connect('127.0.0.1', 6379);
        $key = 'short_link_bloom';
        return $redis->exists($key);
    }
    /**
     * 检查是否有相同URL的记录
     */
    public static function checkDuplicateUrl($pdo, $url, $shortCode = null)
    {
        $sql = "SELECT id FROM short_links WHERE original_url = ?";
        $params = [$url];
        if ($shortCode) {
            $sql .= " AND short_code != ?";
            $params[] = $shortCode;
        }
        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetch() ? true : false;
    }
}

使用示例

<?php
// 前端调用示例
// 1. 生成短链接
$.ajax({
    url: '/api/create.php',
    method: 'POST',
    data: JSON.stringify({
        url: 'https://example.com/very/long/url',
        expire_time: '2024-12-31 23:59:59'
    }),
    contentType: 'application/json',
    success: function(response) {
        console.log('短链接:', response.data.short_url);
    }
});
// 2. 访问短链接
// 用户访问 https://s.yourdomain.com/Ab12Cd34
// 会自动跳转到原始URL

这个完整的短链接系统包含了:

  1. 数据库设计 - 支持过期时间、状态管理
  2. 短码生成 - 使用进制转换算法
  3. 跳转处理 - 支持Apache/Nginx
  4. 安全机制 - 防滥用、防SSRF
  5. 统计功能 - 点击次数统计

根据实际需求,你还可以添加:

  • 自定义短码功能
  • 二维码生成
  • 访问统计分析
  • 用户系统集成

抱歉,评论功能暂时关闭!