PHP 怎么实现文件预览

wen PHP项目 3

本文目录导读:

PHP 怎么实现文件预览

  1. 图片预览
  2. PDF 预览
  3. 文本文件预览
  4. Office 文档预览(使用在线预览服务)
  5. 视频文件预览
  6. 通用文件预览(自动识别类型)
  7. 前端配合使用
  8. 安全注意事项
  9. 最佳实践建议:

在PHP中实现文件预览,通常会根据文件类型采用不同的方案,以下是几种常见的实现方式:

图片预览

<?php
// image_preview.php
$file = $_GET['file'] ?? '';
// 安全校验:防止路径遍历
$allowedPath = '/path/to/uploads/';
$realPath = realpath($allowedPath . $file);
if ($realPath && strpos($realPath, $allowedPath) === 0) {
    $fileInfo = getimagesize($realPath);
    header('Content-Type: ' . $fileInfo['mime']);
    header('Content-Length: ' . filesize($realPath));
    readfile($realPath);
} else {
    http_response_code(404);
    echo 'File not found';
}

PDF 预览

<?php
// pdf_preview.php
$file = $_GET['file'] ?? '';
// 安全校验
$filePath = '/path/to/pdfs/' . basename($file);
if (file_exists($filePath)) {
    header('Content-Type: application/pdf');
    header('Content-Disposition: inline; filename="' . basename($file) . '"');
    header('Content-Length: ' . filesize($filePath));
    readfile($filePath);
} else {
    http_response_code(404);
    echo 'PDF not found';
}

文本文件预览

<?php
// text_preview.php
$file = $_GET['file'] ?? '';
$filePath = '/path/to/texts/' . basename($file);
if (file_exists($filePath)) {
    $content = file_get_contents($filePath);
    // 检测编码
    $encoding = mb_detect_encoding($content, ['UTF-8', 'GB2312', 'GBK'], true);
    if ($encoding != 'UTF-8') {
        $content = mb_convert_encoding($content, 'UTF-8', $encoding);
    }
    header('Content-Type: text/plain; charset=utf-8');
    echo nl2br(htmlspecialchars($content));
} else {
    http_response_code(404);
    echo 'File not found';
}

Office 文档预览(使用在线预览服务)

<?php
// office_preview.php
// 方式1:使用 Microsoft Office Online
$file = $_GET['file'] ?? '';
$fileUrl = 'https://yourdomain.com/uploads/' . urlencode($file);
$previewUrl = 'https://view.officeapps.live.com/op/view.aspx?src=' . urlencode($fileUrl);
header('Location: ' . $previewUrl);
exit;
// 方式2:使用 Google Docs Viewer
// $previewUrl = 'https://docs.google.com/viewer?url=' . urlencode($fileUrl) . '&embedded=true';
?>
<!DOCTYPE html>
<html>
<head>Office 文件预览</title>
</head>
<body>
    <iframe src="https://view.officeapps.live.com/op/embed.aspx?src=<?php echo urlencode($fileUrl); ?>" 
            style="width:100%; height:600px;" frameborder="0">
    </iframe>
</body>
</html>

视频文件预览

<?php
// video_preview.php
$file = $_GET['file'] ?? '';
$filePath = '/path/to/videos/' . basename($file);
if (file_exists($filePath)) {
    // 支持 Range 请求,允许拖动进度条
    $size = filesize($filePath);
    $fp = fopen($filePath, 'rb');
    header('Content-Type: video/mp4');
    header('Accept-Ranges: bytes');
    if (isset($_SERVER['HTTP_RANGE'])) {
        // 处理分段请求
        preg_match('/bytes=(\d+)-(\d*)/', $_SERVER['HTTP_RANGE'], $matches);
        $start = intval($matches[1]);
        $end = isset($matches[2]) && $matches[2] !== '' ? intval($matches[2]) : $size - 1;
        header('HTTP/1.1 206 Partial Content');
        header("Content-Range: bytes $start-$end/$size");
        header('Content-Length: ' . ($end - $start + 1));
        fseek($fp, $start);
        $chunkSize = 8192;
        while ($start <= $end) {
            $data = fread($fp, min($chunkSize, $end - $start + 1));
            echo $data;
            $start += $chunkSize;
        }
    } else {
        header('Content-Length: ' . $size);
        while (!feof($fp)) {
            echo fread($fp, 8192);
            flush();
        }
    }
    fclose($fp);
} else {
    http_response_code(404);
    echo 'Video not found';
}

通用文件预览(自动识别类型)

<?php
// universal_preview.php
class FilePreviewer {
    private $allowedPaths = [
        'images' => ['jpg', 'jpeg', 'png', 'gif', 'webp'],
        'pdf' => ['pdf'],
        'text' => ['txt', 'md', 'log', 'csv', 'xml', 'html'],
        'audio' => ['mp3', 'wav', 'ogg'],
        'video' => ['mp4', 'webm', 'ogg']
    ];
    public function preview($file, $uploadDir) {
        // 安全检查
        $file = basename($file); // 移除路径部分
        $filePath = $uploadDir . '/' . $file;
        if (!file_exists($filePath)) {
            return ['error' => '文件不存在'];
        }
        $extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));
        $mimeType = mime_content_type($filePath);
        // 根据文件类型返回合适的预览结果
        if (in_array($extension, $this->allowedPaths['images'])) {
            return $this->imagePreview($filePath, $mimeType);
        } elseif (in_array($extension, $this->allowedPaths['pdf'])) {
            return $this->pdfPreview($filePath);
        } elseif (in_array($extension, $this->allowedPaths['text'])) {
            return $this->textPreview($filePath);
        } elseif (in_array($extension, $this->allowedPaths['audio'])) {
            return $this->audioPreview($filePath);
        } elseif (in_array($extension, $this->allowedPaths['video'])) {
            return $this->videoPreview($filePath);
        } else {
            return $this->downloadPreview($filePath);
        }
    }
    private function imagePreview($filePath, $mimeType) {
        header('Content-Type: ' . $mimeType);
        readfile($filePath);
        return ['type' => 'image', 'content' => ''];
    }
    private function pdfPreview($filePath) {
        header('Content-Type: application/pdf');
        header('Content-Disposition: inline; filename="' . basename($filePath) . '"');
        readfile($filePath);
        return ['type' => 'pdf', 'content' => ''];
    }
    private function textPreview($filePath) {
        $content = file_get_contents($filePath);
        $encoding = mb_detect_encoding($content, ['UTF-8', 'GB2312', 'GBK'], true);
        if ($encoding != 'UTF-8') {
            $content = mb_convert_encoding($content, 'UTF-8', $encoding);
        }
        header('Content-Type: text/plain; charset=utf-8');
        echo '<pre>' . htmlspecialchars($content) . '</pre>';
        return ['type' => 'text', 'content' => $content];
    }
    private function downloadPreview($filePath) {
        header('Content-Type: application/octet-stream');
        header('Content-Disposition: attachment; filename="' . basename($filePath) . '"');
        readfile($filePath);
        return ['type' => 'download'];
    }
}
// 使用示例
$previewer = new FilePreviewer();
$result = $previewer->preview($_GET['file'] ?? '', '/path/to/uploads');
?>

前端配合使用

<!DOCTYPE html>
<html>
<head>文件预览系统</title>
    <style>
        .preview-container {
            max-width: 1200px;
            margin: 0 auto;
            padding: 20px;
        }
        .file-item {
            display: inline-block;
            margin: 10px;
            padding: 15px;
            border: 1px solid #ddd;
            cursor: pointer;
        }
        .file-item:hover {
            background-color: #f5f5f5;
        }
        .file-icon {
            font-size: 48px;
            text-align: center;
        }
    </style>
</head>
<body>
    <div class="preview-container">
        <h2>文件列表</h2>
        <div id="fileList">
            <?php
            // 显示文件列表,点击进行预览
            $files = glob('/path/to/uploads/*.*');
            foreach ($files as $file) {
                $filename = basename($file);
                $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
                // 根据扩展名显示不同图标
                $icon = '📄';
                if (in_array($extension, ['jpg', 'jpeg', 'png', 'gif'])) $icon = '🖼️';
                elseif ($extension == 'pdf') $icon = '📕';
                elseif (in_array($extension, ['mp4', 'webm'])) $icon = '🎥';
                elseif (in_array($extension, ['mp3', 'wav'])) $icon = '🎵';
                echo "<div class='file-item' onclick=\"previewFile('" . $filename . "')\">";
                echo "<div class='file-icon'>" . $icon . "</div>";
                echo "<div>" . $filename . "</div>";
                echo "</div>";
            }
            ?>
        </div>
        <div id="previewArea" style="margin-top: 20px; display: none;">
            <iframe id="previewFrame" style="width: 100%; height: 600px; border: 1px solid #ddd;"></iframe>
        </div>
    </div>
    <script>
        function previewFile(filename) {
            var previewArea = document.getElementById('previewArea');
            var previewFrame = document.getElementById('previewFrame');
            // 根据文件类型决定预览方式
            previewArea.style.display = 'block';
            previewFrame.src = '/preview.php?file=' + encodeURIComponent(filename);
        }
    </script>
</body>
</html>

安全注意事项

<?php
// security_checks.php
/**
 * 文件预览安全问题
 */
class SecurityChecks {
    // 1. 路径遍历防护
    public function preventPathTraversal($file, $uploadDir) {
        $file = basename($file); // 移除所有路径部分
        $realPath = realpath($uploadDir . '/' . $file);
        $realUploadDir = realpath($uploadDir);
        // 确保文件在允许的目录内
        if ($realPath === false || strpos($realPath, $realUploadDir) !== 0) {
            return false;
        }
        return $file;
    }
    // 2. 文件类型验证
    public function validateFileType($filePath) {
        $allowedTypes = ['image/jpeg', 'image/png', 'application/pdf', 'text/plain'];
        $mimeType = mime_content_type($filePath);
        return in_array($mimeType, $allowedTypes);
    }
    // 3. 文件大小限制
    public function checkFileSize($filePath, $maxSize = 10485760) { // 10MB default
        return filesize($filePath) <= $maxSize;
    }
    // 4. 用户权限验证
    public function checkUserPermission($filePath) {
        // 实现你的权限验证逻辑
        // 例如检查数据库中的文件所有权
        return true;
    }
}
// 综合使用
$file = $_GET['file'] ?? '';
$uploadDir = '/var/www/uploads/';
$security = new SecurityChecks();
$safeFile = $security->preventPathTraversal($file, $uploadDir);
if ($safeFile && $security->validateFileType($uploadDir . '/' . $safeFile) && $security->checkUserPermission($safeFile)) {
    // 执行预览
} else {
    http_response_code(403);
    echo 'Access denied';
}

最佳实践建议:

  1. 安全优先:始终验证文件路径、类型和用户权限
  2. 性能优化:对大文件使用流式传输,避免一次性加载到内存
  3. 缓存策略:对不经常变化的文件设置缓存头,提高性能
  4. 错误处理:提供清晰的错误信息,但不要泄露服务器路径
  5. 前端适配:根据设备类型(桌面/移动)适配预览界面

选择哪种方案取决于你的具体需求、文件类型和服务器环境,对于复杂的 Office 文档,推荐使用微软或 Google 的在线预览服务。

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