PHP文件上传处理完整指南文件上传是Web开发中的常见功能但实现起来需要注意的细节不少。今天写一份完整的指南从基础表单到安全性都覆盖到。先看最基本的文件上传表单。HTML表单需要设置enctypemultipart/form-dataPHP端通过$_FILES接收上传的文件。php// upload.php - 文件上传处理if ($_SERVER[REQUEST_METHOD] POST) {$file $_FILES[upload_file] ?? null;if ($file $file[error] UPLOAD_ERR_OK) {$uploadDir __DIR__ . /uploads/;if (!is_dir($uploadDir)) {mkdir($uploadDir, 0755, true);}$destPath $uploadDir . basename($file[name]);if (move_uploaded_file($file[tmp_name], $destPath)) {echo 文件上传成功: . htmlspecialchars($file[name]) . \n;echo 大小: . number_format($file[size] / 1024, 2) . KB\n;echo 类型: . $file[type] . \n;} else {echo 文件保存失败\n;}} elseif ($file) {$errorMessages [UPLOAD_ERR_INI_SIZE 文件超过PHP配置限制,UPLOAD_ERR_FORM_SIZE 文件超过表单限制,UPLOAD_ERR_PARTIAL 文件只上传了部分,UPLOAD_ERR_NO_FILE 没有选择文件,UPLOAD_ERR_NO_TMP_DIR 服务器缺少临时目录,UPLOAD_ERR_CANT_WRITE 写入磁盘失败,];echo 上传错误: . ($errorMessages[$file[error]] ?? 未知错误) . \n;}}?上传上面的代码功能上能用但安全性不够。生产环境的文件上传要考虑更多因素。文件类型验证是关键的一步。不能只看扩展名还要检查文件的MIME类型和内容。php// 安全的文件上传处理class FileUploadHandler{private array $allowedMimeTypes;private array $allowedExtensions;private int $maxFileSize;private string $uploadDir;public function __construct(array $config []){$this-allowedMimeTypes $config[mime_types] ?? [image/jpeg, image/png, image/gif, image/webp,application/pdf,text/plain,];$this-allowedExtensions $config[extensions] ?? [jpg, jpeg, png, gif, webp, pdf, txt,];$this-maxFileSize $config[max_size] ?? 10 * 1024 * 1024;$this-uploadDir $config[upload_dir] ?? __DIR__ . /uploads;}public function upload(array $file): array{// 检查错误$this-validateError($file);// 检查文件大小$this-validateSize($file);// 检查文件扩展名$extension $this-getExtension($file[name]);$this-validateExtension($extension);// 检查MIME类型基于文件内容$mimeType $this-detectMimeType($file[tmp_name]);$this-validateMimeType($mimeType);// 生成安全的文件名$newFilename $this-generateFilename($extension);// 确保上传目录存在$this-ensureUploadDir();// 移动文件$destPath $this-uploadDir . / . $newFilename;if (!move_uploaded_file($file[tmp_name], $destPath)) {throw new RuntimeException(文件保存失败);}// 如果是图片可以进一步验证if (str_starts_with($mimeType, image/)) {$this-validateImage($destPath);}return [original_name $file[name],saved_name $newFilename,path $destPath,size filesize($destPath),mime_type $mimeType,];}private function validateError(array $file): void{if ($file[error] ! UPLOAD_ERR_OK) {$messages [UPLOAD_ERR_INI_SIZE 文件超过PHP配置限制,UPLOAD_ERR_FORM_SIZE 文件超过表单限制,UPLOAD_ERR_PARTIAL 文件只上传了部分,UPLOAD_ERR_NO_FILE 没有选择文件,UPLOAD_ERR_NO_TMP_DIR 服务器缺少临时目录,UPLOAD_ERR_CANT_WRITE 写入磁盘失败,UPLOAD_ERR_EXTENSION 扩展阻止了上传,];throw new RuntimeException($messages[$file[error]] ?? 未知错误);}}private function validateSize(array $file): void{if ($file[size] $this-maxFileSize) {throw new RuntimeException(sprintf(文件太大%dMB最大允许 %dMB,$file[size] / 1024 / 1024,$this-maxFileSize / 1024 / 1024));}}private function validateExtension(string $extension): void{if (!in_array(strtolower($extension), $this-allowedExtensions)) {throw new RuntimeException(不允许的文件扩展名: $extension);}}private function validateMimeType(string $mimeType): void{if (!in_array($mimeType, $this-allowedMimeTypes)) {throw new RuntimeException(不支持的文件类型: $mimeType);}}private function detectMimeType(string $filePath): string{$finfo finfo_open(FILEINFO_MIME_TYPE);$mimeType finfo_file($finfo, $filePath);finfo_close($finfo);return $mimeType;}private function getExtension(string $filename): string{return strtolower(pathinfo($filename, PATHINFO_EXTENSION));}private function generateFilename(string $extension): string{return bin2hex(random_bytes(16)) . . . $extension;}private function ensureUploadDir(): void{if (!is_dir($this-uploadDir)) {if (!mkdir($this-uploadDir, 0755, true)) {throw new RuntimeException(无法创建上传目录);}}// 创建.htaccess禁止执行PHP$htaccess $this-uploadDir . /.htaccess;if (!file_exists($htaccess)) {file_put_contents($htaccess, php_flag engine off\n);}}private function validateImage(string $imagePath): void{$imageInfo getimagesize($imagePath);if ($imageInfo false) {unlink($imagePath);throw new RuntimeException(图片文件已损坏);}}}// 使用示例$handler new FileUploadHandler([mime_types [image/jpeg, image/png, image/gif],extensions [jpg, jpeg, png, gif],max_size 5 * 1024 * 1024,upload_dir __DIR__ . /uploads,]);if ($_SERVER[REQUEST_METHOD] POST isset($_FILES[file])) {try {$result $handler-upload($_FILES[file]);echo 上传成功: {$result[original_name]} - {$result[saved_name]}\n;} catch (RuntimeException $e) {echo 上传失败: . $e-getMessage() . \n;}}?上传图片图片裁剪和缩略图生成是上传后的常见需求。PHP的GD库可以完成这些操作phpfunction createThumbnail(string $sourcePath, string $destPath, int $maxWidth, int $maxHeight): void{[$origWidth, $origHeight, $imageType] getimagesize($sourcePath);// 计算缩放比例$widthRatio $maxWidth / $origWidth;$heightRatio $maxHeight / $origHeight;$ratio min($widthRatio, $heightRatio);$newWidth (int)($origWidth * $ratio);$newHeight (int)($origHeight * $ratio);// 创建源图像资源$sourceImage match ($imageType) {IMAGETYPE_JPEG imagecreatefromjpeg($sourcePath),IMAGETYPE_PNG imagecreatefrompng($sourcePath),IMAGETYPE_GIF imagecreatefromgif($sourcePath),IMAGETYPE_WEBP imagecreatefromwebp($sourcePath),default throw new InvalidArgumentException(不支持的图片类型),};// 创建目标图像$thumbImage imagecreatetruecolor($newWidth, $newHeight);// 保持PNG透明if ($imageType IMAGETYPE_PNG) {imagealphablending($thumbImage, false);imagesavealpha($thumbImage, true);}// 重新采样imagecopyresampled($thumbImage, $sourceImage,0, 0, 0, 0,$newWidth, $newHeight,$origWidth, $origHeight);// 保存match ($imageType) {IMAGETYPE_JPEG imagejpeg($thumbImage, $destPath, 85),IMAGETYPE_PNG imagepng($thumbImage, $destPath, 9),IMAGETYPE_GIF imagegif($thumbImage, $destPath),IMAGETYPE_WEBP imagewebp($thumbImage, $destPath, 85),};imagedestroy($sourceImage);imagedestroy($thumbImage);}// 上传后生成缩略图$handler new FileUploadHandler();try {$result $handler-upload($_FILES[file]);$thumbPath __DIR__ . /thumbs/ . $result[saved_name];createThumbnail($result[path], $thumbPath, 300, 300);echo 缩略图已生成\n;} catch (RuntimeException $e) {echo 错误: . $e-getMessage() . \n;}?文件上传需要注意的是安全性、大小限制、类型验证和目录权限。我见过太多因为上传功能没做好导致服务器被黑的案例了。上传目录一定要禁止执行PHP脚本不然别人上传一个shell.php你就等着哭吧。
PHP文件上传处理完整指南
PHP文件上传处理完整指南文件上传是Web开发中的常见功能但实现起来需要注意的细节不少。今天写一份完整的指南从基础表单到安全性都覆盖到。先看最基本的文件上传表单。HTML表单需要设置enctypemultipart/form-dataPHP端通过$_FILES接收上传的文件。php// upload.php - 文件上传处理if ($_SERVER[REQUEST_METHOD] POST) {$file $_FILES[upload_file] ?? null;if ($file $file[error] UPLOAD_ERR_OK) {$uploadDir __DIR__ . /uploads/;if (!is_dir($uploadDir)) {mkdir($uploadDir, 0755, true);}$destPath $uploadDir . basename($file[name]);if (move_uploaded_file($file[tmp_name], $destPath)) {echo 文件上传成功: . htmlspecialchars($file[name]) . \n;echo 大小: . number_format($file[size] / 1024, 2) . KB\n;echo 类型: . $file[type] . \n;} else {echo 文件保存失败\n;}} elseif ($file) {$errorMessages [UPLOAD_ERR_INI_SIZE 文件超过PHP配置限制,UPLOAD_ERR_FORM_SIZE 文件超过表单限制,UPLOAD_ERR_PARTIAL 文件只上传了部分,UPLOAD_ERR_NO_FILE 没有选择文件,UPLOAD_ERR_NO_TMP_DIR 服务器缺少临时目录,UPLOAD_ERR_CANT_WRITE 写入磁盘失败,];echo 上传错误: . ($errorMessages[$file[error]] ?? 未知错误) . \n;}}?上传上面的代码功能上能用但安全性不够。生产环境的文件上传要考虑更多因素。文件类型验证是关键的一步。不能只看扩展名还要检查文件的MIME类型和内容。php// 安全的文件上传处理class FileUploadHandler{private array $allowedMimeTypes;private array $allowedExtensions;private int $maxFileSize;private string $uploadDir;public function __construct(array $config []){$this-allowedMimeTypes $config[mime_types] ?? [image/jpeg, image/png, image/gif, image/webp,application/pdf,text/plain,];$this-allowedExtensions $config[extensions] ?? [jpg, jpeg, png, gif, webp, pdf, txt,];$this-maxFileSize $config[max_size] ?? 10 * 1024 * 1024;$this-uploadDir $config[upload_dir] ?? __DIR__ . /uploads;}public function upload(array $file): array{// 检查错误$this-validateError($file);// 检查文件大小$this-validateSize($file);// 检查文件扩展名$extension $this-getExtension($file[name]);$this-validateExtension($extension);// 检查MIME类型基于文件内容$mimeType $this-detectMimeType($file[tmp_name]);$this-validateMimeType($mimeType);// 生成安全的文件名$newFilename $this-generateFilename($extension);// 确保上传目录存在$this-ensureUploadDir();// 移动文件$destPath $this-uploadDir . / . $newFilename;if (!move_uploaded_file($file[tmp_name], $destPath)) {throw new RuntimeException(文件保存失败);}// 如果是图片可以进一步验证if (str_starts_with($mimeType, image/)) {$this-validateImage($destPath);}return [original_name $file[name],saved_name $newFilename,path $destPath,size filesize($destPath),mime_type $mimeType,];}private function validateError(array $file): void{if ($file[error] ! UPLOAD_ERR_OK) {$messages [UPLOAD_ERR_INI_SIZE 文件超过PHP配置限制,UPLOAD_ERR_FORM_SIZE 文件超过表单限制,UPLOAD_ERR_PARTIAL 文件只上传了部分,UPLOAD_ERR_NO_FILE 没有选择文件,UPLOAD_ERR_NO_TMP_DIR 服务器缺少临时目录,UPLOAD_ERR_CANT_WRITE 写入磁盘失败,UPLOAD_ERR_EXTENSION 扩展阻止了上传,];throw new RuntimeException($messages[$file[error]] ?? 未知错误);}}private function validateSize(array $file): void{if ($file[size] $this-maxFileSize) {throw new RuntimeException(sprintf(文件太大%dMB最大允许 %dMB,$file[size] / 1024 / 1024,$this-maxFileSize / 1024 / 1024));}}private function validateExtension(string $extension): void{if (!in_array(strtolower($extension), $this-allowedExtensions)) {throw new RuntimeException(不允许的文件扩展名: $extension);}}private function validateMimeType(string $mimeType): void{if (!in_array($mimeType, $this-allowedMimeTypes)) {throw new RuntimeException(不支持的文件类型: $mimeType);}}private function detectMimeType(string $filePath): string{$finfo finfo_open(FILEINFO_MIME_TYPE);$mimeType finfo_file($finfo, $filePath);finfo_close($finfo);return $mimeType;}private function getExtension(string $filename): string{return strtolower(pathinfo($filename, PATHINFO_EXTENSION));}private function generateFilename(string $extension): string{return bin2hex(random_bytes(16)) . . . $extension;}private function ensureUploadDir(): void{if (!is_dir($this-uploadDir)) {if (!mkdir($this-uploadDir, 0755, true)) {throw new RuntimeException(无法创建上传目录);}}// 创建.htaccess禁止执行PHP$htaccess $this-uploadDir . /.htaccess;if (!file_exists($htaccess)) {file_put_contents($htaccess, php_flag engine off\n);}}private function validateImage(string $imagePath): void{$imageInfo getimagesize($imagePath);if ($imageInfo false) {unlink($imagePath);throw new RuntimeException(图片文件已损坏);}}}// 使用示例$handler new FileUploadHandler([mime_types [image/jpeg, image/png, image/gif],extensions [jpg, jpeg, png, gif],max_size 5 * 1024 * 1024,upload_dir __DIR__ . /uploads,]);if ($_SERVER[REQUEST_METHOD] POST isset($_FILES[file])) {try {$result $handler-upload($_FILES[file]);echo 上传成功: {$result[original_name]} - {$result[saved_name]}\n;} catch (RuntimeException $e) {echo 上传失败: . $e-getMessage() . \n;}}?上传图片图片裁剪和缩略图生成是上传后的常见需求。PHP的GD库可以完成这些操作phpfunction createThumbnail(string $sourcePath, string $destPath, int $maxWidth, int $maxHeight): void{[$origWidth, $origHeight, $imageType] getimagesize($sourcePath);// 计算缩放比例$widthRatio $maxWidth / $origWidth;$heightRatio $maxHeight / $origHeight;$ratio min($widthRatio, $heightRatio);$newWidth (int)($origWidth * $ratio);$newHeight (int)($origHeight * $ratio);// 创建源图像资源$sourceImage match ($imageType) {IMAGETYPE_JPEG imagecreatefromjpeg($sourcePath),IMAGETYPE_PNG imagecreatefrompng($sourcePath),IMAGETYPE_GIF imagecreatefromgif($sourcePath),IMAGETYPE_WEBP imagecreatefromwebp($sourcePath),default throw new InvalidArgumentException(不支持的图片类型),};// 创建目标图像$thumbImage imagecreatetruecolor($newWidth, $newHeight);// 保持PNG透明if ($imageType IMAGETYPE_PNG) {imagealphablending($thumbImage, false);imagesavealpha($thumbImage, true);}// 重新采样imagecopyresampled($thumbImage, $sourceImage,0, 0, 0, 0,$newWidth, $newHeight,$origWidth, $origHeight);// 保存match ($imageType) {IMAGETYPE_JPEG imagejpeg($thumbImage, $destPath, 85),IMAGETYPE_PNG imagepng($thumbImage, $destPath, 9),IMAGETYPE_GIF imagegif($thumbImage, $destPath),IMAGETYPE_WEBP imagewebp($thumbImage, $destPath, 85),};imagedestroy($sourceImage);imagedestroy($thumbImage);}// 上传后生成缩略图$handler new FileUploadHandler();try {$result $handler-upload($_FILES[file]);$thumbPath __DIR__ . /thumbs/ . $result[saved_name];createThumbnail($result[path], $thumbPath, 300, 300);echo 缩略图已生成\n;} catch (RuntimeException $e) {echo 错误: . $e-getMessage() . \n;}?文件上传需要注意的是安全性、大小限制、类型验证和目录权限。我见过太多因为上传功能没做好导致服务器被黑的案例了。上传目录一定要禁止执行PHP脚本不然别人上传一个shell.php你就等着哭吧。