本文目录导读:

我来详细讲解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
这个完整的短链接系统包含了:
- 数据库设计 - 支持过期时间、状态管理
- 短码生成 - 使用进制转换算法
- 跳转处理 - 支持Apache/Nginx
- 安全机制 - 防滥用、防SSRF
- 统计功能 - 点击次数统计
根据实际需求,你还可以添加:
- 自定义短码功能
- 二维码生成
- 访问统计分析
- 用户系统集成