PHP 生成临时下载链接

wen PHP项目 3

本文目录导读:

PHP 生成临时下载链接

  1. 方法1:使用 Token 验证(推荐)
  2. 方法2:使用数据库存储 Token
  3. 方法3:简单签名链接
  4. download.php 文件(统一入口)
  5. 数据库表结构(如果使用数据库方法)
  6. 安全建议

在 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
);

安全建议

  1. 路径安全:始终验证文件路径,防止目录遍历攻击
  2. 密钥安全:使用强随机密钥,不要硬编码
  3. HTTPS:生产环境务必使用HTTPS
  4. 过期时间:合理设置过期时间,避免过长
  5. 访问控制:可以结合IP限制、次数限制等

选择哪种方法取决于您的需求:

  • 方法1:简单,无需数据库,适合临时使用
  • 方法2:可追溯,支持一次性使用,推荐生产环境
  • 方法3:无需数据库,安全可靠,性能好

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