本文目录导读:

我来帮你设计一个PHP短链接与参数追踪系统,这是一个完整的解决方案,包含短链接生成、跳转和参数追踪功能。
数据库结构
-- 短链接表 CREATE TABLE `short_links` ( `id` int(11) NOT NULL AUTO_INCREMENT, `short_code` varchar(10) NOT NULL COMMENT '短码', `original_url` text NOT NULL COMMENT '原始URL', varchar(255) DEFAULT NULL COMMENT '链接标题', `user_id` int(11) DEFAULT NULL COMMENT '创建用户ID', `expire_at` datetime DEFAULT NULL COMMENT '过期时间', `is_active` tinyint(1) DEFAULT '1' COMMENT '是否启用', `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_user_id` (`user_id`), KEY `idx_created_at` (`created_at`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 访问追踪表 CREATE TABLE `link_tracking` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `short_link_id` int(11) NOT NULL COMMENT '短链接ID', `ip_address` varchar(45) DEFAULT NULL COMMENT '访问IP', `user_agent` text COMMENT '用户代理', `referer` text COMMENT '来源URL', `country` varchar(100) DEFAULT NULL COMMENT '国家', `city` varchar(100) DEFAULT NULL COMMENT '城市', `device_type` varchar(20) DEFAULT NULL COMMENT '设备类型(desktop/mobile/tablet)', `os` varchar(50) DEFAULT NULL COMMENT '操作系统', `browser` varchar(50) DEFAULT NULL COMMENT '浏览器', `is_click` tinyint(1) DEFAULT '1' COMMENT '是否点击', `click_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '点击时间', `parameters` json DEFAULT NULL COMMENT '自定义参数', PRIMARY KEY (`id`), KEY `idx_short_link_id` (`short_link_id`), KEY `idx_click_time` (`click_time`), KEY `idx_country` (`country`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 链接参数配置表 CREATE TABLE `link_parameters` ( `id` int(11) NOT NULL AUTO_INCREMENT, `short_link_id` int(11) NOT NULL, `param_name` varchar(50) NOT NULL COMMENT '参数名', `param_value` varchar(255) DEFAULT NULL COMMENT '参数值', `is_required` tinyint(1) DEFAULT '0' COMMENT '是否必填', `created_at` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_short_link_id` (`short_link_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
短链接核心类
<?php
// ShortLinkService.php
class ShortLinkService {
private $db;
private $config;
public function __construct($db, $config = []) {
$this->db = $db;
$this->config = array_merge([
'short_code_length' => 6,
'allowed_chars' => 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
'base_url' => 'https://yourdomain.com/'
], $config);
}
/**
* 生成短链接
*/
public function createShortLink($originalUrl, $title = '', $userId = null, $expireAt = null, $parameters = []) {
// 验证URL
if (!filter_var($originalUrl, FILTER_VALIDATE_URL)) {
throw new Exception('Invalid URL');
}
// 生成唯一短码
$shortCode = $this->generateUniqueCode();
// 构建原始URL参数
if (!empty($parameters)) {
$queryParams = [];
foreach ($parameters as $param => $value) {
$queryParams[] = urlencode($param) . '=' . urlencode($value);
}
$separator = (strpos($originalUrl, '?') === false) ? '?' : '&';
$originalUrl .= $separator . implode('&', $queryParams);
}
// 插入数据库
$stmt = $this->db->prepare("
INSERT INTO short_links (short_code, original_url, title, user_id, expire_at)
VALUES (?, ?, ?, ?, ?)
");
$stmt->execute([$shortCode, $originalUrl, $title, $userId, $expireAt]);
$linkId = $this->db->lastInsertId();
// 保存参数配置
if (!empty($parameters)) {
$this->saveLinkParameters($linkId, $parameters);
}
return [
'id' => $linkId,
'short_code' => $shortCode,
'short_url' => $this->config['base_url'] . $shortCode,
'original_url' => $originalUrl
];
}
/**
* 生成唯一短码
*/
private function generateUniqueCode() {
$chars = $this->config['allowed_chars'];
$maxAttempts = 10;
for ($i = 0; $i < $maxAttempts; $i++) {
$code = '';
for ($j = 0; $j < $this->config['short_code_length']; $j++) {
$code .= $chars[random_int(0, strlen($chars) - 1)];
}
// 检查是否已存在
$stmt = $this->db->prepare("SELECT id FROM short_links WHERE short_code = ?");
$stmt->execute([$code]);
if (!$stmt->fetch()) {
return $code;
}
}
throw new Exception('Failed to generate unique code');
}
/**
* 保存链接参数配置
*/
private function saveLinkParameters($linkId, $parameters) {
$stmt = $this->db->prepare("
INSERT INTO link_parameters (short_link_id, param_name, param_value)
VALUES (?, ?, ?)
");
foreach ($parameters as $name => $value) {
$stmt->execute([$linkId, $name, $value]);
}
}
/**
* 获取并重定向
*/
public function redirect($shortCode) {
// 查询短链接
$stmt = $this->db->prepare("
SELECT id, original_url, expire_at, is_active
FROM short_links
WHERE short_code = ? AND is_active = 1
");
$stmt->execute([$shortCode]);
$link = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$link) {
header("HTTP/1.0 404 Not Found");
exit('Link not found');
}
// 检查过期
if ($link['expire_at'] && strtotime($link['expire_at']) < time()) {
header("HTTP/1.0 410 Gone");
exit('Link has expired');
}
// 记录访问信息
$this->trackVisit($link['id']);
// 重定向
header("Location: " . $link['original_url'], true, 302);
exit();
}
/**
* 追踪访问信息
*/
private function trackVisit($shortLinkId) {
$ip = $this->getClientIP();
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
$referer = $_SERVER['HTTP_REFERER'] ?? '';
// 解析设备信息
$deviceInfo = $this->parseUserAgent($userAgent);
// 获取地理位置(可选,需要IP地理定位服务)
$geoData = $this->getGeoLocation($ip);
// 收集URL参数
$parameters = [];
if (isset($_SERVER['QUERY_STRING']) && !empty($_SERVER['QUERY_STRING'])) {
parse_str($_SERVER['QUERY_STRING'], $parameters);
}
try {
$stmt = $this->db->prepare("
INSERT INTO link_tracking
(short_link_id, ip_address, user_agent, referer, country, city,
device_type, os, browser, parameters)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
");
$stmt->execute([
$shortLinkId,
$ip,
$userAgent,
$referer,
$geoData['country'] ?? null,
$geoData['city'] ?? null,
$deviceInfo['device_type'],
$deviceInfo['os'],
$deviceInfo['browser'],
json_encode($parameters)
]);
} catch (Exception $e) {
// 日志记录错误但不中断重定向
error_log("Tracking failed: " . $e->getMessage());
}
}
/**
* 获取客户端IP
*/
private function getClientIP() {
$headers = [
'HTTP_X_FORWARDED_FOR',
'HTTP_X_REAL_IP',
'HTTP_CLIENT_IP',
'REMOTE_ADDR'
];
foreach ($headers as $header) {
if (isset($_SERVER[$header])) {
$ips = explode(',', $_SERVER[$header]);
$ip = trim($ips[0]);
if (filter_var($ip, FILTER_VALIDATE_IP)) {
return $ip;
}
}
}
return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}
/**
* 解析User-Agent
*/
private function parseUserAgent($userAgent) {
$result = [
'device_type' => 'desktop',
'os' => 'Unknown',
'browser' => 'Unknown'
];
if (empty($userAgent)) {
return $result;
}
// 设备类型
if (preg_match('/Mobile|Android|iPhone|iPad|iPod/i', $userAgent)) {
$result['device_type'] = 'mobile';
if (preg_match('/iPad|Tablet/i', $userAgent)) {
$result['device_type'] = 'tablet';
}
}
// 操作系统
$osPatterns = [
'Windows' => '/Windows NT/i',
'Mac OS' => '/Mac OS X|Macintosh/i',
'Linux' => '/Linux/i',
'Android' => '/Android/i',
'iOS' => '/iPhone|iPad|iPod/i'
];
foreach ($osPatterns as $os => $pattern) {
if (preg_match($pattern, $userAgent)) {
$result['os'] = $os;
break;
}
}
// 浏览器
$browserPatterns = [
'Chrome' => '/Chrome/i',
'Firefox' => '/Firefox/i',
'Safari' => '/Safari/i',
'Edge' => '/Edge|Edg/i',
'Opera' => '/Opera|OPR/i'
];
foreach ($browserPatterns as $browser => $pattern) {
if (preg_match($pattern, $userAgent)) {
$result['browser'] = $browser;
break;
}
}
return $result;
}
/**
* 获取地理位置(示例使用ip-api.com)
*/
private function getGeoLocation($ip) {
// 忽略内网IP
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
try {
$response = @file_get_contents("http://ip-api.com/json/{$ip}");
if ($response) {
$data = json_decode($response, true);
if ($data['status'] === 'success') {
return [
'country' => $data['country'],
'city' => $data['city']
];
}
}
} catch (Exception $e) {
// 忽略错误
}
}
return [];
}
/**
* 获取统计信息
*/
public function getStatistics($shortLinkId, $startDate = null, $endDate = null) {
$conditions = ['short_link_id = ?'];
$params = [$shortLinkId];
if ($startDate) {
$conditions[] = 'click_time >= ?';
$params[] = $startDate;
}
if ($endDate) {
$conditions[] = 'click_time <= ?';
$params[] = $endDate;
}
$where = implode(' AND ', $conditions);
$stats = [];
// 总点击量
$stmt = $this->db->prepare("SELECT COUNT(*) as total FROM link_tracking WHERE {$where}");
$stmt->execute($params);
$stats['total_clicks'] = $stmt->fetchColumn();
// 独立IP
$stmt = $this->db->prepare("SELECT COUNT(DISTINCT ip_address) as unique_ip FROM link_tracking WHERE {$where}");
$stmt->execute($params);
$stats['unique_visitors'] = $stmt->fetchColumn();
// 按国家统计
$stmt = $this->db->prepare("
SELECT country, COUNT(*) as count
FROM link_tracking
WHERE {$where} AND country IS NOT NULL
GROUP BY country
ORDER BY count DESC
LIMIT 10
");
$stmt->execute($params);
$stats['by_country'] = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 按设备统计
$stmt = $this->db->prepare("
SELECT device_type, COUNT(*) as count
FROM link_tracking
WHERE {$where}
GROUP BY device_type
");
$stmt->execute($params);
$stats['by_device'] = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 按日期统计
$stmt = $this->db->prepare("
SELECT DATE(click_time) as date, COUNT(*) as count
FROM link_tracking
WHERE {$where}
GROUP BY DATE(click_time)
ORDER BY date
");
$stmt->execute($params);
$stats['by_date'] = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 参数统计
$stmt = $this->db->prepare("
SELECT parameters, COUNT(*) as count
FROM link_tracking
WHERE {$where} AND parameters IS NOT NULL
GROUP BY parameters
LIMIT 20
");
$stmt->execute($params);
$stats['by_parameters'] = $stmt->fetchAll(PDO::FETCH_ASSOC);
return $stats;
}
}
路由处理 (跳转逻辑)
<?php
// redirect.php - 短链接跳转处理
require_once 'database.php'; // 数据库连接
require_once 'ShortLinkService.php';
$db = new PDO('mysql:host=localhost;dbname=your_db', 'username', 'password');
$shortLinkService = new ShortLinkService($db);
// 获取短码
$shortCode = $_GET['code'] ?? '';
if (empty($shortCode)) {
header("HTTP/1.0 400 Bad Request");
exit('Missing short code');
}
// 执行跳转并追踪
$shortLinkService->redirect($shortCode);
创建短链接API
<?php
// api_create.php - 创建短链接API
require_once 'database.php';
require_once 'ShortLinkService.php';
header('Content-Type: application/json');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
exit;
}
$db = new PDO('mysql:host=localhost;dbname=your_db', 'username', 'password');
$shortLinkService = new ShortLinkService($db);
try {
$data = json_decode(file_get_contents('php://input'), true);
$originalUrl = $data['url'] ?? '';
$title = $data['title'] ?? '';
$expireAt = $data['expire_at'] ?? null;
$parameters = $data['parameters'] ?? [];
$result = $shortLinkService->createShortLink(
$originalUrl,
$title,
null, // user_id
$expireAt,
$parameters
);
echo json_encode([
'success' => true,
'data' => $result
]);
} catch (Exception $e) {
http_response_code(400);
echo json_encode([
'success' => false,
'error' => $e->getMessage()
]);
}
统计面板查询
<?php
// api_stats.php - 统计查询API
require_once 'database.php';
require_once 'ShortLinkService.php';
header('Content-Type: application/json');
$db = new PDO('mysql:host=localhost;dbname=your_db', 'username', 'password');
$shortLinkService = new ShortLinkService($db);
$linkId = $_GET['link_id'] ?? 0;
$startDate = $_GET['start_date'] ?? null;
$endDate = $_GET['end_date'] ?? null;
if (!$linkId) {
http_response_code(400);
echo json_encode(['error' => 'Missing link_id']);
exit;
}
try {
$stats = $shortLinkService->getStatistics($linkId, $startDate, $endDate);
echo json_encode([
'success' => true,
'data' => $stats
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode([
'success' => false,
'error' => $e->getMessage()
]);
}
管理界面示例
<!DOCTYPE html>
<html>
<head>短链接管理系统</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.container { max-width: 800px; margin: auto; }
.form-group { margin-bottom: 15px; }
label { display: block; margin-bottom: 5px; font-weight: bold; }
input[type="text"], input[type="url"] { width: 100%; padding: 8px; }
.btn { padding: 10px 20px; background: #007bff; color: white; border: none; cursor: pointer; }
.result { margin-top: 20px; padding: 15px; background: #f0f0f0; }
.param-row { margin-bottom: 10px; }
</style>
</head>
<body>
<div class="container">
<h1>创建短链接</h1>
<form id="createLinkForm">
<div class="form-group">
<label>原始URL:</label>
<input type="url" id="originalUrl" required>
</div>
<div class="form-group">
<label>标题 (可选):</label>
<input type="text" id="title">
</div>
<div class="form-group">
<label>自定义参数:</label>
<div id="parametersContainer">
<div class="param-row">
<input type="text" placeholder="参数名" class="param-name">
<input type="text" placeholder="参数值" class="param-value">
<button type="button" onclick="addParam()">+</button>
</div>
</div>
</div>
<button type="submit" class="btn">生成短链接</button>
</form>
<div id="result" class="result" style="display:none;"></div>
</div>
<script>
function addParam() {
const container = document.getElementById('parametersContainer');
const row = document.createElement('div');
row.className = 'param-row';
row.innerHTML = `
<input type="text" placeholder="参数名" class="param-name">
<input type="text" placeholder="参数值" class="param-value">
<button type="button" onclick="this.parentElement.remove()">-</button>
`;
container.appendChild(row);
}
document.getElementById('createLinkForm').addEventListener('submit', function(e) {
e.preventDefault();
const parameters = {};
document.querySelectorAll('.param-row').forEach(row => {
const name = row.querySelector('.param-name').value;
const value = row.querySelector('.param-value').value;
if (name && value) {
parameters[name] = value;
}
});
const data = {
url: document.getElementById('originalUrl').value,
title: document.getElementById('title').value,
parameters: parameters
};
fetch('/api_create.php', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(result => {
const resultDiv = document.getElementById('result');
resultDiv.style.display = 'block';
if (result.success) {
resultDiv.innerHTML = `
<h3>生成成功!</h3>
<p><strong>短链接:</strong> ${result.data.short_url}</p>
<p><strong>原始URL:</strong> ${result.data.original_url}</p>
`;
} else {
resultDiv.innerHTML = `<p style="color:red;">错误: ${result.error}</p>`;
}
});
});
</script>
</body>
</html>
使用示例
创建带追踪参数的短链接
// 使用示例
$shortLinkService = new ShortLinkService($db);
// 创建带UTM参数的短链接
$result = $shortLinkService->createShortLink(
'https://example.com/product',
'产品推广链接',
null,
'2024-12-31 23:59:59',
[
'utm_source' => 'facebook',
'utm_medium' => 'social',
'utm_campaign' => 'summer_sale',
'utm_content' => 'banner_1',
'ref' => 'user123'
]
);
.htaccess 配置 (Apache)
RewriteEngine On RewriteRule ^([a-zA-Z0-9]+)$ redirect.php?code=$1 [L]
Nginx 配置
location / {
try_files $uri $uri/ /redirect.php?code=$1;
}
location ~ ^/([a-zA-Z0-9]+)$ {
rewrite ^/(.*)$ /redirect.php?code=$1 last;
}
- 短链接生成:安全随机生成,避免冲突
- 参数追踪:支持自定义参数(UTM等)
- 访问统计:IP、设备、地理位置、浏览器等
- 过期管理:支持链接过期时间
- 设备识别:自动识别桌面/移动/平板
- API接口:RESTful API便于集成
- 可视化统计:按国家、设备、日期等维度
这个系统可以轻松集成到现有的PHP项目中,支持高并发和大规模使用,你可以根据需求扩展更多功能,比如二维码生成、批量导入、A/B测试等。