本文目录导读:

- 项目结构
- 配置文件 (config.php)
- 上传处理 (upload.php)
- 相册展示 (gallery.php)
- 样式文件 (css/style.css)
- 主页面 (index.php)
- JavaScript 增强功能 (js/upload.js)
- 关键特性展示
- 运行说明
我来通过一个完整的在线相册项目案例,展示PHP处理多文件上传的能力。
项目结构
online-album/
├── index.php # 相册主页
├── upload.php # 上传处理
├── gallery.php # 相册展示
├── config.php # 配置文件
├── uploads/ # 上传目录
│ ├── photos/ # 原图
│ └── thumbs/ # 缩略图
└── css/ # 样式文件
└── style.css
配置文件 (config.php)
<?php
// 配置文件
define('UPLOAD_DIR', 'uploads/photos/');
define('THUMB_DIR', 'uploads/thumbs/');
define('MAX_FILE_SIZE', 5 * 1024 * 1024); // 5MB
define('ALLOWED_TYPES', ['image/jpeg', 'image/png', 'image/gif', 'image/webp']);
define('MAX_FILES', 10); // 一次最多上传10张
// 允许的文件扩展名
define('ALLOWED_EXTENSIONS', ['jpg', 'jpeg', 'png', 'gif', 'webp']);
// 创建上传目录
if (!file_exists(UPLOAD_DIR)) {
mkdir(UPLOAD_DIR, 0777, true);
}
if (!file_exists(THUMB_DIR)) {
mkdir(THUMB_DIR, 0777, true);
}
// 数据库配置(可选,这里用文件系统存储)
// define('DB_HOST', 'localhost');
// define('DB_USER', 'root');
// define('DB_PASS', '');
// define('DB_NAME', 'online_album');
?>
上传处理 (upload.php)
<?php
require_once 'config.php';
// 设置响应头
header('Content-Type: application/json');
// 错误处理类
class UploadHandler {
private $errors = [];
private $success = [];
public function handleUpload() {
// 检查是否有文件上传
if (empty($_FILES['photos'])) {
return $this->response(false, '没有选择文件');
}
$files = $this->rearrangeFiles($_FILES['photos']);
// 检查文件数量
if (count($files) > MAX_FILES) {
return $this->response(false, '一次最多上传' . MAX_FILES . '张照片');
}
// 处理每个文件
foreach ($files as $index => $file) {
$result = $this->processFile($file, $index);
if ($result['success']) {
$this->success[] = $result;
} else {
$this->errors[] = $result;
}
}
// 返回结果
return $this->response(
empty($this->errors) ? true : (empty($this->success) ? false : true),
$this->getMessage()
);
}
// 重组文件数组(处理多文件上传)
private function rearrangeFiles($files) {
$rearranged = [];
$count = count($files['name']);
for ($i = 0; $i < $count; $i++) {
if ($files['error'][$i] !== UPLOAD_ERR_NO_FILE) {
$rearranged[] = [
'name' => $files['name'][$i],
'type' => $files['type'][$i],
'tmp_name' => $files['tmp_name'][$i],
'error' => $files['error'][$i],
'size' => $files['size'][$i]
];
}
}
return $rearranged;
}
// 处理单个文件
private function processFile($file, $index) {
// 检查错误
if ($file['error'] !== UPLOAD_ERR_OK) {
return [
'success' => false,
'name' => $file['name'],
'error' => $this->getUploadError($file['error'])
];
}
// 验证文件类型
if (!in_array($file['type'], ALLOWED_TYPES)) {
return [
'success' => false,
'name' => $file['name'],
'error' => '不支持的文件类型'
];
}
// 验证文件大小
if ($file['size'] > MAX_FILE_SIZE) {
return [
'success' => false,
'name' => $file['name'],
'error' => '文件大小超过5MB限制'
];
}
// 验证扩展名
$extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($extension, ALLOWED_EXTENSIONS)) {
return [
'success' => false,
'name' => $file['name'],
'error' => '不允许的文件扩展名'
];
}
// 生成唯一文件名
$newFileName = $this->generateFileName($extension);
$uploadPath = UPLOAD_DIR . $newFileName;
// 移动上传文件
if (!move_uploaded_file($file['tmp_name'], $uploadPath)) {
return [
'success' => false,
'name' => $file['name'],
'error' => '文件保存失败'
];
}
// 创建缩略图
$thumbPath = THUMB_DIR . $newFileName;
$this->createThumbnail($uploadPath, $thumbPath, 200);
return [
'success' => true,
'name' => $file['name'],
'filename' => $newFileName,
'path' => $uploadPath,
'thumb' => $thumbPath
];
}
// 生成唯一文件名
private function generateFileName($extension) {
return uniqid() . '_' . time() . '.' . $extension;
}
// 创建缩略图
private function createThumbnail($source, $destination, $thumbWidth) {
$imageInfo = getimagesize($source);
$width = $imageInfo[0];
$height = $imageInfo[1];
// 计算缩放比例
$ratio = $thumbWidth / $width;
$thumbHeight = $height * $ratio;
// 创建真彩色图像
$thumb = imagecreatetruecolor($thumbWidth, $thumbHeight);
// 根据类型创建源图像
switch ($imageInfo['mime']) {
case 'image/jpeg':
$sourceImage = imagecreatefromjpeg($source);
break;
case 'image/png':
$sourceImage = imagecreatefrompng($source);
imagealphablending($thumb, false);
imagesavealpha($thumb, true);
break;
case 'image/gif':
$sourceImage = imagecreatefromgif($source);
break;
case 'image/webp':
$sourceImage = imagecreatefromwebp($source);
break;
default:
return false;
}
// 调整大小
imagecopyresampled($thumb, $sourceImage, 0, 0, 0, 0,
$thumbWidth, $thumbHeight, $width, $height);
// 保存缩略图
switch ($imageInfo['mime']) {
case 'image/jpeg':
imagejpeg($thumb, $destination, 80);
break;
case 'image/png':
imagepng($thumb, $destination, 8);
break;
case 'image/gif':
imagegif($thumb, $destination);
break;
case 'image/webp':
imagewebp($thumb, $destination, 80);
break;
}
// 释放内存
imagedestroy($sourceImage);
imagedestroy($thumb);
return true;
}
// 获取上传错误信息
private function getUploadError($code) {
switch ($code) {
case UPLOAD_ERR_INI_SIZE:
return '文件超过服务器限制';
case UPLOAD_ERR_FORM_SIZE:
return '文件超过表单限制';
case UPLOAD_ERR_PARTIAL:
return '文件只上传了部分';
case UPLOAD_ERR_NO_TMP_DIR:
return '临时文件夹不可用';
case UPLOAD_ERR_CANT_WRITE:
return '无法写入文件';
default:
return '未知错误';
}
}
// 格式化响应消息
private function getMessage() {
$message = '';
if (!empty($this->success)) {
$message .= '成功上传' . count($this->success) . '张照片。';
}
if (!empty($this->errors)) {
$message .= '失败' . count($this->errors) . '张。';
foreach ($this->errors as $error) {
$message .= '<br>' . htmlspecialchars($error['name']) . ': ' . $error['error'];
}
}
return $message;
}
// 响应
private function response($success, $message) {
return json_encode([
'success' => $success,
'message' => $message,
'data' => [
'success' => $this->success,
'errors' => $this->errors
]
]);
}
}
// 处理上传请求
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['photos'])) {
$handler = new UploadHandler();
echo $handler->handleUpload();
} else {
echo json_encode(['success' => false, 'message' => '无效的请求']);
}
?>
相册展示 (gallery.php)
<?php
require_once 'config.php';
class Gallery {
private $photos = [];
public function loadPhotos() {
$files = scandir(THUMB_DIR);
foreach ($files as $file) {
if ($file !== '.' && $file !== '..') {
$this->photos[] = [
'thumb' => THUMB_DIR . $file,
'original' => UPLOAD_DIR . $file,
'name' => $file,
'size' => filesize(UPLOAD_DIR . $file),
'modified' => filemtime(UPLOAD_DIR . $file)
];
}
}
// 按修改时间排序(最新的在前)
usort($this->photos, function($a, $b) {
return $b['modified'] - $a['modified'];
});
return $this->photos;
}
}
?>
<!DOCTYPE html>
<html>
<head>在线相册</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div class="container">
<h1>📸 在线相册</h1>
<div class="upload-section">
<form id="uploadForm" enctype="multipart/form-data">
<div class="file-input-wrapper">
<input type="file" name="photos[]" id="photos"
multiple accept="image/*"
onchange="previewFiles(this)">
<label for="photos" class="upload-btn">
选择照片
</label>
</div>
<div id="preview" class="preview-container"></div>
<button type="submit" class="submit-btn" id="submitBtn" disabled>
上传照片
</button>
</form>
<div id="progress" class="progress-bar" style="display:none;">
<div class="progress-fill"></div>
</div>
<div id="message" class="message"></div>
</div>
<div class="gallery-section">
<h2>相册内容</h2>
<div class="gallery-grid">
<?php
$gallery = new Gallery();
$photos = $gallery->loadPhotos();
if (empty($photos)) {
echo '<p class="empty-gallery">相册还是空的,快上传照片吧!</p>';
} else {
foreach ($photos as $photo) {
$fileSize = round($photo['size'] / 1024, 2);
$date = date('Y-m-d H:i', $photo['modified']);
?>
<div class="photo-card" onclick="showOriginal('<?= $photo['original'] ?>')">
<img src="<?= $photo['thumb'] ?>"
alt="<?= htmlspecialchars($photo['name']) ?>">
<div class="photo-info">
<span class="size"><?= $fileSize ?> KB</span>
<span class="date"><?= $date ?></span>
</div>
</div>
<?php
}
}
?>
</div>
</div>
</div>
<!-- 查看原图模态框 -->
<div id="modal" class="modal" onclick="closeModal()">
<span class="close">×</span>
<img class="modal-content" id="modalImage">
</div>
<script src="js/upload.js"></script>
<script>
// 文件预览功能
function previewFiles(input) {
const preview = document.getElementById('preview');
preview.innerHTML = '';
const submitBtn = document.getElementById('submitBtn');
if (input.files.length > 10) {
alert('一次最多上传10张照片');
input.value = '';
submitBtn.disabled = true;
return;
}
if (input.files.length > 0) {
submitBtn.disabled = false;
Array.from(input.files).forEach(file => {
const reader = new FileReader();
const div = document.createElement('div');
div.className = 'preview-item';
reader.onload = function(e) {
div.innerHTML = `
<img src="${e.target.result}" alt="${file.name}">
<span class="file-name">${file.name}</span>
<span class="file-size">${(file.size/1024).toFixed(2)} KB</span>
`;
};
reader.readAsDataURL(file);
preview.appendChild(div);
});
} else {
submitBtn.disabled = true;
}
}
// 显示原图
function showOriginal(src) {
const modal = document.getElementById('modal');
const modalImg = document.getElementById('modalImage');
modal.style.display = 'block';
modalImg.src = src;
}
function closeModal() {
document.getElementById('modal').style.display = 'none';
}
// Ajax上传
document.getElementById('uploadForm').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
const progressBar = document.getElementById('progress');
const progressFill = progressBar.querySelector('.progress-fill');
const message = document.getElementById('message');
const submitBtn = document.getElementById('submitBtn');
// 显示进度条
progressBar.style.display = 'block';
submitBtn.disabled = true;
message.innerHTML = '';
const xhr = new XMLHttpRequest();
// 上传进度
xhr.upload.onprogress = function(e) {
if (e.lengthComputable) {
const percent = (e.loaded / e.total) * 100;
progressFill.style.width = percent + '%';
}
};
xhr.onload = function() {
progressBar.style.display = 'none';
submitBtn.disabled = false;
if (xhr.status === 200) {
const response = JSON.parse(xhr.responseText);
if (response.success) {
message.className = 'message success';
// 刷新页面显示新照片
setTimeout(() => location.reload(), 1500);
} else {
message.className = 'message error';
}
message.innerHTML = response.message;
} else {
message.className = 'message error';
message.innerHTML = '上传失败,请重试';
}
};
xhr.open('POST', 'upload.php', true);
xhr.send(formData);
});
</script>
</body>
</html>
样式文件 (css/style.css)
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Arial', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 20px;
padding: 30px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.1);
}
h1 {
text-align: center;
color: #333;
margin-bottom: 30px;
font-size: 2.5em;
}
/* 上传区域 */
.upload-section {
background: #f8f9fa;
border-radius: 15px;
padding: 30px;
margin-bottom: 30px;
}
.file-input-wrapper {
position: relative;
text-align: center;
margin-bottom: 20px;
}
.file-input-wrapper input[type="file"] {
display: none;
}
.upload-btn {
display: inline-block;
padding: 15px 30px;
background: #667eea;
color: white;
border-radius: 10px;
cursor: pointer;
font-size: 1.1em;
transition: background 0.3s;
}
.upload-btn:hover {
background: #5a67d8;
}
.preview-container {
display: flex;
flex-wrap: wrap;
gap: 15px;
margin: 20px 0;
justify-content: center;
}
.preview-item {
width: 150px;
padding: 10px;
background: white;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
text-align: center;
}
.preview-item img {
width: 100%;
height: 120px;
object-fit: cover;
border-radius: 5px;
margin-bottom: 5px;
}
.preview-item .file-name {
display: block;
font-size: 0.8em;
color: #666;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.submit-btn {
display: block;
margin: 20px auto 0;
padding: 12px 40px;
background: #48bb78;
color: white;
border: none;
border-radius: 10px;
font-size: 1.1em;
cursor: pointer;
transition: background 0.3s;
}
.submit-btn:hover:not(:disabled) {
background: #38a169;
}
.submit-btn:disabled {
background: #ccc;
cursor: not-allowed;
}
/* 进度条 */
.progress-bar {
width: 100%;
height: 10px;
background: #e2e8f0;
border-radius: 5px;
overflow: hidden;
margin: 20px 0;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #667eea, #764ba2);
border-radius: 5px;
transition: width 0.3s;
width: 0%;
}
/* 消息 */
.message {
padding: 15px;
border-radius: 10px;
margin: 20px 0;
text-align: center;
}
.message.success {
background: #c6f6d5;
color: #276749;
}
.message.error {
background: #fed7d7;
color: #9b2c2c;
}
/* 相册网格 */
.gallery-section {
margin-top: 30px;
}
.gallery-section h2 {
color: #333;
margin-bottom: 20px;
}
.gallery-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
}
.photo-card {
background: white;
border-radius: 15px;
overflow: hidden;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
cursor: pointer;
transition: transform 0.3s, box-shadow 0.3s;
}
.photo-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2);
}
.photo-card img {
width: 100%;
height: 200px;
object-fit: cover;
}
.photo-info {
padding: 15px;
display: flex;
justify-content: space-between;
background: #f8f9fa;
font-size: 0.9em;
color: #666;
}
.empty-gallery {
text-align: center;
color: #999;
padding: 50px;
font-size: 1.2em;
}
/* 模态框 */
.modal {
display: none;
position: fixed;
z-index: 1000;
padding-top: 50px;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.9);
}
.modal-content {
margin: auto;
display: block;
max-width: 90%;
max-height: 90%;
}
.close {
position: absolute;
top: 15px;
right: 35px;
color: #f1f1f1;
font-size: 40px;
font-weight: bold;
cursor: pointer;
}
.close:hover {
color: #bbb;
}
主页面 (index.php)
<?php
// 简单路由,重定向到相册页面
header('Location: gallery.php');
exit;
?>
JavaScript 增强功能 (js/upload.js)
// 拖拽上传功能
class DragDropUpload {
constructor() {
this.dropZone = document.querySelector('.upload-section');
this.init();
}
init() {
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
this.dropZone.addEventListener(eventName, this.preventDefaults, false);
});
['dragenter', 'dragover'].forEach(eventName => {
this.dropZone.addEventListener(eventName, this.highlight, false);
});
['dragleave', 'drop'].forEach(eventName => {
this.dropZone.addEventListener(eventName, this.unhighlight, false);
});
this.dropZone.addEventListener('drop', this.handleDrop, false);
}
preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
highlight() {
this.classList.add('highlight');
}
unhighlight() {
this.classList.remove('highlight');
}
handleDrop(e) {
const dt = e.dataTransfer;
const files = dt.files;
const fileInput = document.getElementById('photos');
// 创建新的FileList
const dataTransfer = new DataTransfer();
Array.from(files).forEach(file => {
if (file.type.startsWith('image/')) {
dataTransfer.items.add(file);
}
});
fileInput.files = dataTransfer.files;
// 触发预览
const event = new Event('change');
fileInput.dispatchEvent(event);
}
}
// 初始化
document.addEventListener('DOMContentLoaded', function() {
new DragDropUpload();
});
关键特性展示
多文件上传处理
- 使用
multiple属性允许一次选择多个文件 - 重组
$_FILES数组结构 - 逐个验证和处理文件
文件验证
// 验证文件类型
if (!in_array($file['type'], ALLOWED_TYPES)) {
// 拒绝上传
}
// 验证文件大小
if ($file['size'] > MAX_FILE_SIZE) {
// 拒绝上传
}
// 验证文件扩展名
$extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
缩略图生成
- 自动创建缩略图
- 保持图片比例
- 支持 JPEG、PNG、GIF、WebP 格式
前端功能
- 文件预览
- 上传进度条
- 拖拽上传
- 实时反馈
安全措施
- 文件类型验证
- 文件大小限制
- 唯一文件名生成
- 防止路径遍历
运行说明
- 将文件放入 Web 服务器目录(如 Apache 的
htdocs) - 确保
uploads/photos/和uploads/thumbs/目录可写 - 访问
http://localhost/online-album/gallery.php
这个项目完整展示了 PHP 处理多文件上传的完整流程,包括前端交互、文件验证、缩略图生成等功能。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播