本文目录导读:

在 PHP 中生成临时下载链接有多种方法,我为您提供几种常用的实现方案:
方法1:使用 Token 验证(推荐)
生成临时链接
<?php
class TempDownload {
private $expireTime = 3600; // 默认1小时过期
private $secretKey = 'your-secret-key'; // 密钥
/**
* 生成临时下载链接
* @param string $filePath 文件路径
* @param int $expireTime 过期时间(秒)
* @return string 临时链接
*/
public function generateLink($filePath, $expireTime = null) {
if ($expireTime) {
$this->expireTime = $expireTime;
}
// 生成过期时间戳
$expire = time() + $this->expireTime;
// 生成token
$token = md5($filePath . $expire . $this->secretKey);
// 构建链接
$baseUrl = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http') . "://$_SERVER[HTTP_HOST]";
return $baseUrl . '/download.php?file=' . urlencode($filePath) . '&expire=' . $expire . '&token=' . $token;
}
/**
* 验证并处理下载
*/
public function handleDownload() {
if (!isset($_GET['file']) || !isset($_GET['expire']) || !isset($_GET['token'])) {
die('无效的下载链接');
}
$file = urldecode($_GET['file']);
$expire = $_GET['expire'];
$token = $_GET['token'];
// 验证token
$expectedToken = md5($file . $expire . $this->secretKey);
if ($token !== $expectedToken) {
die('无效的下载令牌');
}
// 检查是否过期
if (time() > $expire) {
die('下载链接已过期');
}
// 验证文件存在
if (!file_exists($file) || !is_file($file)) {
die('文件不存在');
}
// 发送文件
$this->sendFile($file);
}
/**
* 发送文件下载
*/
private function sendFile($filePath) {
// 获取文件名
$fileName = basename($filePath);
// 设置请求头
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $fileName . '"');
header('Content-Length: ' . filesize($filePath));
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Expires: 0');
// 清空输出缓冲
while (ob_get_level()) {
ob_end_clean();
}
// 读取并输出文件
readfile($filePath);
exit;
}
}
// 使用示例
$tempDownload = new TempDownload();
// 生成链接
$filePath = '/path/to/your/file.zip';
$link = $tempDownload->generateLink($filePath, 3600); // 1小时有效
echo "下载链接: " . $link;
?>
方法2:使用数据库存储 Token
<?php
class DatabaseTempDownload {
private $db;
private $expireTime = 3600;
public function __construct($pdo) {
$this->db = $pdo;
}
/**
* 生成链接并存储到数据库
*/
public function generateLink($filePath, $expireTime = null) {
if ($expireTime) {
$this->expireTime = $expireTime;
}
$token = bin2hex(random_bytes(32)); // 生成随机token
$expire = date('Y-m-d H:i:s', time() + $this->expireTime);
// 保存到数据库
$sql = "INSERT INTO download_links (token, file_path, expire_time, created_at) VALUES (?, ?, ?, NOW())";
$stmt = $this->db->prepare($sql);
$stmt->execute([$token, $filePath, $expire]);
$baseUrl = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http') . "://$_SERVER[HTTP_HOST]";
return $baseUrl . "/download.php?token=" . $token;
}
/**
* 验证token并下载
*/
public function handleDownload() {
if (!isset($_GET['token'])) {
die('无效的下载链接');
}
$token = $_GET['token'];
// 查询数据库
$sql = "SELECT * FROM download_links WHERE token = ? AND expire_time > NOW() LIMIT 1";
$stmt = $this->db->prepare($sql);
$stmt->execute([$token]);
$record = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$record) {
die('下载链接无效或已过期');
}
// 可以使用后删除记录(一次性使用)
$sql = "DELETE FROM download_links WHERE id = ?";
$stmt = $this->db->prepare($sql);
$stmt->execute([$record['id']]);
// 发送文件
$this->sendFile($record['file_path']);
}
private function sendFile($filePath) {
if (!file_exists($filePath)) {
die('文件不存在');
}
$fileName = basename($filePath);
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $fileName . '"');
header('Content-Length: ' . filesize($filePath));
while (ob_get_level()) {
ob_end_clean();
}
readfile($filePath);
exit;
}
}
?>
方法3:简单签名链接
<?php
class SimpleSignedLink {
private $secretKey = 'your-secret-key';
private $expireTime = 600; // 10分钟
public function generateLink($filePath, $expireTime = null) {
if ($expireTime) {
$this->expireTime = $expireTime;
}
$expire = time() + $this->expireTime;
// 生成签名
$signature = hash_hmac('sha256', $filePath . $expire, $this->secretKey);
$baseUrl = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http') . "://$_SERVER[HTTP_HOST]";
return $baseUrl . '/download.php?file=' . urlencode($filePath) . '&expire=' . $expire . '&signature=' . $signature;
}
public function validateAndDownload() {
if (!isset($_GET['file']) || !isset($_GET['expire']) || !isset($_GET['signature'])) {
die('无效请求');
}
$file = urldecode($_GET['file']);
$expire = $_GET['expire'];
$signature = $_GET['signature'];
// 验证签名
$expectedSignature = hash_hmac('sha256', $file . $expire, $this->secretKey);
if (!hash_equals($expectedSignature, $signature)) {
die('签名无效');
}
// 检查过期
if (time() > $expire) {
die('链接已过期');
}
// 验证文件路径(防止目录遍历攻击)
$allowedDir = '/var/www/files/';
$realPath = realpath($allowedDir . $file);
if ($realPath === false || strpos($realPath, $allowedDir) !== 0) {
die('无效的文件路径');
}
// 发送文件
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . basename($realPath) . '"');
header('Content-Length: ' . filesize($realPath));
while (ob_get_level()) {
ob_end_clean();
}
readfile($realPath);
exit;
}
}
?>
download.php 文件(统一入口)
<?php // download.php - 处理所有临时下载请求 // 根据您的实现方法选择 $download = new TempDownload(); // 或 DatabaseTempDownload / SimpleSignedLink $download->handleDownload();
数据库表结构(如果使用数据库方法)
CREATE TABLE download_links (
id INT AUTO_INCREMENT PRIMARY KEY,
token VARCHAR(64) UNIQUE NOT NULL,
file_path VARCHAR(255) NOT NULL,
expire_time DATETIME NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
安全建议
- 路径安全:始终验证文件路径,防止目录遍历攻击
- 密钥安全:使用强随机密钥,不要硬编码
- HTTPS:生产环境务必使用HTTPS
- 过期时间:合理设置过期时间,避免过长
- 访问控制:可以结合IP限制、次数限制等
选择哪种方法取决于您的需求:
- 方法1:简单,无需数据库,适合临时使用
- 方法2:可追溯,支持一次性使用,推荐生产环境
- 方法3:无需数据库,安全可靠,性能好