
本文详解如何在 php 中为多文件上传添加可靠的大小验证逻辑,避免因误用 $_files 数组结构导致的验证失效问题,并结合 phpmailer 实现安全、健壮的邮件附件发送流程。
本文详解如何在 php 中为多文件上传添加可靠的大小验证逻辑,避免因误用 $_files 数组结构导致的验证失效问题,并结合 phpmailer 实现安全、健壮的邮件附件发送流程。
在使用 PHP 处理多文件上传并集成至邮件系统(如 PHPMailer)时,文件大小验证失效是高频问题——代码看似完整,却无法阻止超限文件被附加并发送。根本原因往往并非逻辑缺失,而是对 $_FILES 超全局数组结构的误解。下面我们将从结构解析、验证实现、错误处理到完整示例,系统性构建一套可落地的解决方案。
✅ 正确理解 $_FILES 的嵌套结构
PHP 将每个同名多文件字段(如 )解析为一个二维数组,其键为 name、type、tmp_name、error、size —— 全部位于 $_FILES['uploaded-file'] 下一级,而非嵌套在 ['name'] 内部。
❌ 错误写法(导致始终跳过验证):
$_FILES['uploaded-file']['name'][$i]['size'] // 不存在! $_FILES['uploaded-file']['name'][$i] > $max_size // 比较的是文件名字符串,非字节数!
✅ 正确路径:
立即学习“PHP免费学习笔记(深入)”;
$_FILES['uploaded-file']['size'][$i] // ✅ 当前第 i 个文件的大小(字节) $_FILES['uploaded-file']['error'][$i] // ✅ 当前第 i 个文件的上传错误码 $_FILES['uploaded-file']['tmp_name'][$i] // ✅ 临时文件路径(用于 move_uploaded_file) $_FILES['uploaded-file']['name'][$i] // ✅ 原始文件名(需过滤/转义)
✅ 完整、健壮的文件大小验证流程
以下为修正后的核心逻辑,已整合进 PHPMailer 发送流程,并包含关键防护点:
<?php
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
require 'vendor/autoload.php';
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
die('Invalid request method.');
}
// 防垃圾邮件(蜜罐)
if (!empty(trim($_POST['honey'] ?? ''))) {
http_response_code(403);
echo "SPAM detected.";
exit;
}
// 配置基础信息
$user_name = $_POST['name'] ?? '';
$user_email = filter_var($_POST['email'] ?? '', FILTER_SANITIZE_EMAIL);
$user_message = $_POST['message'] ?? '';
$user_phone = $_POST['phone'] ?? '';
$max_size = 2 * 1024 * 1024; // 2 MB(注意:1024×1024,原文 1204 是笔误)
// ✅ 关键:检查 $_FILES 是否存在且非空(使用 count > 0 更可靠)
$attachments = $_FILES['uploaded-file'] ?? null;
$has_files = is_array($attachments) && !empty($attachments['name']);
$mail = new PHPMailer(true);
try {
$mail->isMail();
$mail->setFrom($user_email, $user_name);
$mail->addAddress('contact@example.com'); // 替换为目标邮箱
$mail->isHTML(true);
$mail->Subject = 'Zapytanie ze strony www';
$mail->Body = "Telefon: $user_phone<br><br>Treść wiadomości:<br>" . nl2br(htmlspecialchars($user_message));
$mail->AltBody = "Telefon: $user_phone\n\nTreść wiadomości:\n" . $user_message;
// ✅ 处理附件:逐个验证 + 安全移动 + 附加
if ($has_files) {
$upload_dir = __DIR__ . '/uploads/';
if (!is_dir($upload_dir) && !mkdir($upload_dir, 0755, true)) {
throw new Exception('Upload directory not writable.');
}
$file_count = count($attachments['name']);
for ($i = 0; $i < $file_count; $i++) {
$tmp_path = $attachments['tmp_name'][$i] ?? '';
$orig_name = $attachments['name'][$i] ?? '';
$size = $attachments['size'][$i] ?? 0;
$error = $attachments['error'][$i] ?? UPLOAD_ERR_NO_FILE;
// ✅ 忽略空文件或上传失败项
if ($error !== UPLOAD_ERR_OK) {
error_log("File upload error #{$error} at index {$i}");
continue;
}
// ✅ 核心:大小验证(单位:字节)
if ($size === 0 || $size > $max_size) {
throw new Exception("File '{$orig_name}' exceeds maximum size of " .
number_format($max_size / 1024, 0) . " KB.");
}
// ✅ 安全生成唯一文件名(防覆盖 & XSS)
$safe_name = preg_replace('/[^a-zA-Z0-9._-]/', '_', $orig_name);
$unique_name = uniqid() . '_' . $safe_name;
$target_path = $upload_dir . $unique_name;
// ✅ 移动临时文件(失败则抛异常)
if (!move_uploaded_file($tmp_path, $target_path)) {
throw new Exception("Failed to save uploaded file: {$orig_name}");
}
// ✅ 添加至邮件(使用绝对路径更可靠)
$mail->addAttachment($target_path, $orig_name);
}
}
$mail->send();
header('Location: sent.html');
exit;
} catch (Exception $e) {
error_log('Mailer exception: ' . $e->getMessage());
echo "Error: " . htmlspecialchars($e->getMessage());
}⚠️ 关键注意事项与最佳实践
- $_FILES 判空逻辑:isset($_FILES['xxx']) 仅判断字段是否存在,不保证有有效文件;应结合 count($_FILES['xxx']['name']) > 0 或检查 $_FILES['xxx']['error'][0] !== UPLOAD_ERR_NO_FILE。
- 错误码优先级:始终先检查 $_FILES['...']['error'][$i],再检查 size。网络中断、用户取消上传等均会导致 error ≠ UPLOAD_ERR_OK,此时 size 可能为 0 或不可信。
- 单位一致性:$_FILES['...']['size'] 单位为字节,2 * 1024 * 1024 = 2MB(原文 1204 为明显笔误,已修正)。
- 临时文件清理:move_uploaded_file() 成功后,PHP 会自动清理临时文件;若验证失败未移动,则需手动 unlink($tmp_path)(本例中因 continue 跳过,故无需额外清理)。
-
安全性加固:
- 使用 filter_var(..., FILTER_SANITIZE_EMAIL) 过滤邮箱;
- 对输出内容使用 htmlspecialchars() 防 XSS;
- 重命名上传文件(移除特殊字符+加随机前缀),避免路径遍历或执行风险;
- 设置 uploads/ 目录无脚本执行权限(如 Apache 中配置 Options -ExecCGI)。
通过以上重构,你的文件大小验证将真正生效:任何单个附件超过 2MB 时,脚本将立即终止并返回明确错误,绝不会进入邮件发送环节。这不仅是修复 Bug,更是构建生产级表单处理流程的必要基石。











