PHP 文件上传

PHP 文件上传中文讲解

什么是 PHP 文件上传?

PHP 文件上传是指通过 HTML 表单允许用户上传文件(如图片、文档等)到服务器,并使用 PHP 脚本处理这些文件。PHP 通过超全局变量 $_FILES 获取上传文件的信息,适用于图片上传、文档管理、用户头像设置等场景。文件上传需要特别注意安全性和验证,以防止恶意文件或攻击。

为什么使用 PHP 文件上传?

  • 用户交互:允许用户上传文件,如头像、附件或媒体文件。
  • 数据存储:将文件保存到服务器,用于后续访问或处理。
  • 动态应用:支持文件管理功能,如内容管理系统(CMS)或社交平台。
  • 典型场景:用户注册时的头像上传、博客附件上传、文件分享系统。

核心概念

  • HTML 表单:使用 <form> 标签,设置 method="post"enctype="multipart/form-data"
  • $_FILES 数组:包含上传文件的元数据,如文件名、类型、大小、临时路径等。
  • 安全验证:检查文件类型、大小、扩展名,防止恶意文件上传。
  • 文件移动:使用 move_uploaded_file() 将临时文件移到目标目录。

$_FILES 数组结构

当用户上传文件时,PHP 将信息存储在 $_FILES 数组中。例如,表单字段 <input type="file" name="upload">

  • $_FILES['upload']['name']:原始文件名(如 image.jpg)。
  • $_FILES['upload']['type']:MIME 类型(如 image/jpeg)。
  • $_FILES['upload']['size']:文件大小(字节)。
  • $_FILES['upload']['tmp_name']:临时文件路径(如 /tmp/php123)。
  • $_FILES['upload']['error']:错误代码(如 UPLOAD_ERR_OK 表示成功)。

常见错误代码

  • UPLOAD_ERR_OK (0):上传成功。
  • UPLOAD_ERR_INI_SIZE (1):文件大小超过 php.iniupload_max_filesize
  • UPLOAD_ERR_FORM_SIZE (2):文件大小超过表单的 MAX_FILE_SIZE
  • UPLOAD_ERR_NO_FILE (4):未上传文件。
  • 更多错误代码见 PHP 官方手册

基本示例:文件上传

以下是一个简单的文件上传示例,包含验证和错误处理。

  1. HTML 表单 (upload.html):
   <!DOCTYPE html>
   <html lang="zh-CN">
   <head>
       <meta charset="UTF-8">
       <title>文件上传</title>
       <style>
           .error { color: red; }
           label { display: inline-block; width: 100px; }
           input { margin-bottom: 10px; }
       </style>
   </head>
   <body>
       <h2>上传文件</h2>
       <form action="upload.php" method="post" enctype="multipart/form-data">
           <input type="hidden" name="MAX_FILE_SIZE" value="2097152"> <!-- 2MB -->
           <label for="file">选择文件*:</label>
           <input type="file" id="file" name="file">
           <br>
           <input type="submit" value="上传">
           <p>* 表示必需字段</p>
       </form>
   </body>
   </html>
  1. PHP 处理脚本 (upload.php):
   <?php
   $errors = [];
   $uploadDir = 'uploads/'; // 目标目录

   // 确保上传目录存在且可写
   if (!is_dir($uploadDir)) {
       mkdir($uploadDir, 0755, true);
   }

   if ($_SERVER['REQUEST_METHOD'] === 'POST') {
       // 检查文件是否上传
       if (!isset($_FILES['file']) || $_FILES['file']['error'] === UPLOAD_ERR_NO_FILE) {
           $errors[] = "请上传一个文件";
       } else {
           $file = $_FILES['file'];
           $name = $file['name'];
           $tmp_name = $file['tmp_name'];
           $size = $file['size'];
           $error = $file['error'];

           // 处理上传错误
           if ($error !== UPLOAD_ERR_OK) {
               switch ($error) {
                   case UPLOAD_ERR_INI_SIZE:
                   case UPLOAD_ERR_FORM_SIZE:
                       $errors[] = "文件大小超过限制(最大 2MB)";
                       break;
                   default:
                       $errors[] = "上传失败,错误代码: $error";
               }
           } else {
               // 验证文件大小(2MB = 2 * 1024 * 1024 字节)
               if ($size > 2 * 1024 * 1024) {
                   $errors[] = "文件大小不能超过 2MB";
               }

               // 验证文件扩展名
               $allowed = ['jpg', 'jpeg', 'png', 'pdf'];
               $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
               if (!in_array($ext, $allowed)) {
                   $errors[] = "只允许上传 JPG、PNG 或 PDF 文件";
               }

               // 如果没有错误,移动文件
               if (empty($errors)) {
                   $newPath = $uploadDir . uniqid() . '.' . $ext; // 使用唯一文件名
                   if (move_uploaded_file($tmp_name, $newPath)) {
                       echo "文件上传成功: <a href='$newPath'>$name</a>";
                   } else {
                       $errors[] = "无法保存文件到服务器";
                   }
               }
           }
       }
   }

   // 显示错误并回显表单
   if (!empty($errors)) {
       echo "<div class='error'><ul>";
       foreach ($errors as $error) {
           echo "<li>$error</li>";
       }
       echo "</ul></div>";
   }
   include 'upload.html';
   ?>

关键点解析

  • 表单设置
  • enctype="multipart/form-data":必须设置以支持文件上传。
  • MAX_FILE_SIZE:隐藏字段限制文件大小(仅客户端提示,需服务器验证)。
  • $_FILES:处理上传文件的元数据,检查 error 代码判断上传状态。
  • 验证
  • 检查文件是否存在(UPLOAD_ERR_NO_FILE)。
  • 限制文件大小(2MB)。
  • 限制文件类型(仅允许 JPG、PNG、PDF)。
  • 文件移动:使用 move_uploaded_file() 将临时文件移到目标目录,防止覆盖使用 uniqid() 生成唯一文件名。
  • 错误处理:集中存储错误,统一显示,并回显表单。

安全性注意事项

  1. 防止恶意文件
  • 验证文件扩展名和 MIME 类型:
    php $allowedTypes = ['image/jpeg', 'image/png', 'application/pdf']; if (!in_array($file['type'], $allowedTypes)) { $errors[] = "无效的文件类型"; }
  • 检查文件内容(高级验证):
    php if (in_array($ext, ['jpg', 'jpeg', 'png']) && !getimagesize($tmp_name)) { $errors[] = "无效的图片文件"; }
  1. 防止路径穿越
  • 不要直接使用用户提供的文件名:
    php $safeName = basename($name); // 提取文件名,防止路径攻击
  1. 目录权限
  • 确保上传目录可写(通常 755 或 775 权限)。
  • 避免将上传目录放在 Web 可访问路径下,或使用 .htaccess 限制访问:
    <Directory "uploads"> Deny from all </Directory>
  1. CSRF 保护
    添加 CSRF 令牌:
   session_start();
   $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
   ?>
   <input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>">

结合数据库存储

将文件信息存储到数据库,记录路径和元数据:

  1. 数据库表 (files):
   CREATE TABLE files (
       id INT AUTO_INCREMENT PRIMARY KEY,
       filename VARCHAR(255) NOT NULL,
       path VARCHAR(255) NOT NULL,
       size INT NOT NULL,
       upload_time DATETIME NOT NULL
   );
  1. 修改 PHP 脚本 (upload.php):
   // 在成功移动文件后添加
   if (move_uploaded_file($tmp_name, $newPath)) {
       try {
           $pdo = new PDO("mysql:host=localhost;dbname=test", "user", "pass");
           $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
           $stmt = $pdo->prepare("INSERT INTO files (filename, path, size, upload_time) VALUES (:filename, :path, :size, :upload_time)");
           $stmt->execute([
               'filename' => $name,
               'path' => $newPath,
               'size' => $size,
               'upload_time' => date('Y-m-d H:i:s')
           ]);
           echo "文件上传并记录到数据库!";
       } catch (PDOException $e) {
           $errors[] = "数据库错误: " . $e->getMessage();
       }
   }

最佳实践

  • 验证文件类型:结合扩展名和 MIME 类型验证,防止伪造。
  • 限制大小:在 php.ini 设置 upload_max_filesizepost_max_size
  upload_max_filesize = 2M
  post_max_size = 8M
  • 唯一文件名:使用 uniqid() 或哈希生成文件名,避免覆盖。
  • 错误提示:提供具体错误信息,如“只允许 JPG 文件”。
  • 安全存储:将文件存储在非 Web 可访问目录,数据库记录路径。
  • 客户端验证:结合 HTML5 属性(如 accept):
  <input type="file" name="file" accept="image/jpeg,image/png,application/pdf">

参考资源

总结

PHP 文件上传通过 $_FILESmove_uploaded_file() 实现,结合验证(类型、大小)和安全措施(如 CSRF、路径清理)确保安全性和可靠性。上述示例展示了从表单设计到文件存储的完整流程,适用于图片上传、文档管理等场景。通过结合数据库存储和错误处理,开发者可以构建用户友好的文件上传功能。

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注