本文目录导读:

我来详细介绍PHP项目文件上传功能的开发步骤和最佳实践。
基础文件上传表单
<!-- upload.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">文件上传</title>
</head>
<body>
<form action="upload.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="MAX_FILE_SIZE" value="5242880">
<input type="file" name="file" accept=".jpg,.jpeg,.png,.gif,.pdf">
<button type="submit">上传文件</button>
</form>
</body>
</html>
基础上传处理PHP代码
<?php
// upload.php
header('Content-Type: text/html; charset=utf-8');
// 检查是否有文件上传
if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_FILES['file'])) {
die('请选择文件上传');
}
$file = $_FILES['file'];
// 检查上传错误
if ($file['error'] !== UPLOAD_ERR_OK) {
$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 => '服务器写入失败',
UPLOAD_ERR_EXTENSION => '文件上传被扩展阻止'
];
die($errorMessages[$file['error']] ?? '未知错误');
}
安全的文件上传处理
<?php
class FileUploader {
private $allowedTypes = [
'image/jpeg',
'image/png',
'image/gif',
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
];
private $maxFileSize = 5242880; // 5MB
private $uploadDir = 'uploads/';
public function __construct($config = []) {
if (isset($config['allowed_types'])) {
$this->allowedTypes = $config['allowed_types'];
}
if (isset($config['max_file_size'])) {
$this->maxFileSize = $config['max_file_size'];
}
if (isset($config['upload_dir'])) {
$this->uploadDir = $config['upload_dir'];
}
// 创建上传目录
if (!is_dir($this->uploadDir)) {
mkdir($this->uploadDir, 0755, true);
}
}
public function upload($file) {
// 验证文件
if (!$this->validate($file)) {
return ['success' => false, 'message' => '文件验证失败'];
}
// 生成安全的文件名
$extension = pathinfo($file['name'], PATHINFO_EXTENSION);
$fileName = $this->generateFileName($extension);
$filePath = $this->uploadDir . $fileName;
// 移动文件
if (!move_uploaded_file($file['tmp_name'], $filePath)) {
return ['success' => false, 'message' => '文件保存失败'];
}
return [
'success' => true,
'filename' => $fileName,
'path' => $filePath,
'size' => $file['size'],
'type' => $file['type']
];
}
private function validate($file) {
// 检查文件类型
if (!in_array($file['type'], $this->allowedTypes)) {
throw new Exception('不支持的文件类型');
}
// 检查文件大小
if ($file['size'] > $this->maxFileSize) {
throw new Exception('文件大小超过限制');
}
// 验证MIME类型
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
if (!in_array($mimeType, $this->allowedTypes)) {
throw new Exception('文件MIME类型不匹配');
}
return true;
}
private function generateFileName($extension) {
// 生成唯一文件名:时间戳+随机字符串
$timestamp = time();
$randomStr = bin2hex(random_bytes(8));
return "{$timestamp}_{$randomStr}.{$extension}";
}
}
图像文件特殊处理
<?php
class ImageUploader extends FileUploader {
private $allowedImageTypes = ['image/jpeg', 'image/png', 'image/gif'];
private $maxWidth = 1920;
private $maxHeight = 1080;
public function __construct($config = []) {
$config['allowed_types'] = $this->allowedImageTypes;
parent::__construct($config);
}
public function uploadImage($file, $options = []) {
$result = $this->upload($file);
if ($result['success']) {
// 压缩图片
$this->compressImage($result['path']);
// 生成缩略图
if (isset($options['thumbnail'])) {
$thumbnailPath = $this->createThumbnail($result['path'], $options['thumbnail']);
$result['thumbnail'] = $thumbnailPath;
}
}
return $result;
}
private function compressImage($filePath) {
list($width, $height) = getimagesize($filePath);
// 如果图片太大,进行缩放
if ($width > $this->maxWidth || $height > $this->maxHeight) {
$scale = min($this->maxWidth / $width, $this->maxHeight / $height);
$newWidth = $width * $scale;
$newHeight = $height * $scale;
$srcImage = imagecreatefromstring(file_get_contents($filePath));
$destImage = imagecreatetruecolor($newWidth, $newHeight);
imagecopyresampled($destImage, $srcImage, 0, 0, 0, 0,
$newWidth, $newHeight, $width, $height);
// 保存压缩后的图片
$info = pathinfo($filePath);
switch ($info['extension']) {
case 'jpg':
case 'jpeg':
imagejpeg($destImage, $filePath, 85);
break;
case 'png':
imagepng($destImage, $filePath, 6);
break;
case 'gif':
imagegif($destImage, $filePath);
break;
}
imagedestroy($srcImage);
imagedestroy($destImage);
}
}
private function createThumbnail($filePath, $size = 200) {
$info = pathinfo($filePath);
$thumbnailPath = $info['dirname'] . '/thumb_' . $info['basename'];
list($width, $height) = getimagesize($filePath);
// 计算缩略图尺寸
if ($width > $height) {
$newWidth = $size;
$newHeight = $height * ($size / $width);
} else {
$newHeight = $size;
$newWidth = $width * ($size / $height);
}
$srcImage = imagecreatefromstring(file_get_contents($filePath));
$destImage = imagecreatetruecolor($newWidth, $newHeight);
imagecopyresampled($destImage, $srcImage, 0, 0, 0, 0,
$newWidth, $newHeight, $width, $height);
// 保存缩略图
switch ($info['extension']) {
case 'jpg':
case 'jpeg':
imagejpeg($destImage, $thumbnailPath, 90);
break;
case 'png':
imagepng($destImage, $thumbnailPath, 5);
break;
case 'gif':
imagegif($destImage, $thumbnailPath);
break;
}
imagedestroy($srcImage);
imagedestroy($destImage);
return $thumbnailPath;
}
}
多文件上传处理
<?php
// 多文件上传示例
if (isset($_FILES['files'])) {
$files = $_FILES['files'];
$uploader = new FileUploader();
$results = [];
// 处理每个文件
for ($i = 0; $i < count($files['name']); $i++) {
if ($files['error'][$i] === UPLOAD_ERR_OK) {
$file = [
'name' => $files['name'][$i],
'type' => $files['type'][$i],
'tmp_name' => $files['tmp_name'][$i],
'error' => $files['error'][$i],
'size' => $files['size'][$i]
];
$result = $uploader->upload($file);
$results[] = $result;
}
}
}
AJAX文件上传
// upload.js - 前端AJAX上传
function uploadFile(file) {
const formData = new FormData();
formData.append('file', file);
// 显示进度条
showProgressBar();
return fetch('upload.php', {
method: 'POST',
body: formData,
// 监听上传进度
onUploadProgress: function(progressEvent) {
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
updateProgressBar(percentCompleted);
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
showSuccessMessage(data.filename);
} else {
showErrorMessage(data.message);
}
})
.catch(error => {
showErrorMessage('上传失败:' + error.message);
});
}
php.ini配置建议
; php.ini 文件上传相关配置 file_uploads = On upload_max_filesize = 20M post_max_size = 25M max_file_uploads = 20 upload_tmp_dir = /tmp
安全最佳实践
<?php
// 安全配置类
class SecurityConfig {
public static function getSecurityHeaders() {
return [
'X-Content-Type-Options' => 'nosniff',
'X-Frame-Options' => 'DENY',
'Content-Security-Policy' => "default-src 'self'"
];
}
public static function secureUpload($filePath) {
// 禁用执行权限
chmod($filePath, 0644);
// 重命名文件为安全名称
$pathInfo = pathinfo($filePath);
$safeName = preg_replace('/[^a-zA-Z0-9._-]/', '', $pathInfo['basename']);
// 检查文件内容是否包含恶意代码
$content = file_get_contents($filePath);
if (preg_match('/<\?(php|=)|<script/i', $content)) {
unlink($filePath);
throw new Exception('文件包含可疑代码');
}
return true;
}
}
使用示例
<?php
// 使用示例
try {
// 基本文件上传
$uploader = new FileUploader([
'upload_dir' => 'uploads/' . date('Y/m/d'),
'max_file_size' => 10 * 1024 * 1024 // 10MB
]);
$result = $uploader->upload($_FILES['file']);
// 图片上传(带压缩和缩略图)
$imageUploader = new ImageUploader();
$result = $imageUploader->uploadImage($_FILES['image'], [
'thumbnail' => 200
]);
if ($result['success']) {
echo "文件上传成功:" . $result['filename'];
}
} catch (Exception $e) {
echo "上传失败:" . $e->getMessage();
}
注意事项
- 文件验证:始终验证文件类型、大小和内容
- 安全存储:文件存储在web根目录外或使用重定向
- 权限控制:设置适当的文件权限
- 错误处理:完善的错误处理和日志记录
- 并发处理:考虑大文件上传的并发限制
- 文件清理:定期清理临时文件
这个方案提供了完整、安全的文件上传功能,可以根据实际需求进行调整和扩展。