本文目录导读:

我来详细讲解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
- 统计功能 - 点击次数统计
根据实际需求,你还可以添加:
- 自定义短码功能
- 二维码生成
- 访问统计分析
- 用户系统集成
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播