第十二章-PHP文件上传

第十二章-PHP文件上传

一,文件上传原理

一、HTTP协议与文件上传

1. 请求体结构
  • 当表单设置enctype="multipart/form-data"时,浏览器会将表单数据编码为多部分(multipart)格式

  • Boundary分隔符:随机生成的字符串(如----WebKitFormBoundaryABC123),用于分隔表单字段和文件内容。

  • 请求头示例

    Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryABC123
    
  • 请求体示例

    ------WebKitFormBoundaryABC123
    Content-Disposition: form-data; name="userfile"; filename="photo.jpg"
    Content-Type: image/jpeg[文件二进制数据]
    ------WebKitFormBoundaryABC123--
    
2. 数据分块传输
  • 大文件上传时,HTTP协议支持分块传输(Transfer-Encoding: chunked),但PHP会自动重组完整数据。

二、PHP服务端处理机制

1. 接收与解析
  • 数据流处理:PHP通过SAPI(Server API)接收原始HTTP请求数据。
  • 临时文件生成
    • PHP将上传的文件内容写入临时目录(sys_get_temp_dir()),默认路径由php.iniupload_tmp_dir指定。
    • 临时文件名随机生成(如/tmp/phpA3b4cD),与原始文件名无关
2. $_FILES数组结构
  • PHP自动解析请求体,提取文件信息并填充到$_FILES数组中:

    $_FILES['userfile'] = ['name'     => 'photo.jpg',        // 客户端原始文件名'type'     => 'image/jpeg',       // 浏览器提供的MIME类型(可能被篡改)'tmp_name' => '/tmp/phpA3b4cD',   // 临时文件路径'error'    => UPLOAD_ERR_OK,      // 错误码'size'     => 102400              // 文件大小(字节)
    ];
    
3. 临时文件生命周期
  • 自动清理:如果未调用move_uploaded_file(),脚本结束时PHP自动删除临时文件。
  • 手动管理:可通过register_shutdown_function()自定义清理逻辑。

三、核心安全机制

1. move_uploaded_file()的安全性
  • 防路径注入:自动检查目标路径是否包含../等非法字符。
  • 防伪造上传:验证文件是否通过HTTP POST上传(避免直接操作临时文件)。
2. 文件类型验证
  • MIME类型检测

    • 使用finfo_file()(基于文件内容签名,非扩展名)。

    • 示例:检测JPEG文件的真实MIME类型:

      $finfo = finfo_open(FILEINFO_MIME_TYPE);
      $mime = finfo_file($finfo, $_FILES['file']['tmp_name']);
      // 返回 'image/jpeg' 而非客户端提供的可能伪造值
      
  • 扩展名白名单

    $allowedExts = ['jpg', 'jpeg', 'png'];
    $ext = strtolower(pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION));
    if (!in_array($ext, $allowedExts)) {die("非法文件扩展名");
    }
    
3. 防目录遍历攻击
  • 使用basename()过滤文件名中的路径符号:

    $safeFilename = basename($_FILES['file']['name']);
    

四、服务器配置详解(php.ini)

配置项默认值作用
file_uploadsOn是否允许HTTP文件上传
upload_max_filesize2M单个文件最大大小
post_max_size8MPOST请求最大数据量(必须大于上传限制)
upload_tmp_dir系统临时目录临时文件存储路径
max_file_uploads20单次请求允许上传的最大文件数

配置关系

post_max_size >= upload_max_filesize * max_file_uploads

五、完整上传流程

  1. 客户端提交表单
    • 浏览器将文件编码为multipart/form-data格式。
    • 分块传输至服务器(对用户透明)。
  2. 服务端接收数据
    • Web服务器(如Nginx/Apache)接收原始数据流。
    • PHP SAPI解析请求体,生成临时文件。
  3. PHP脚本处理
    • 访问$_FILES获取文件信息。
    • 执行错误检查、安全验证、文件移动。
  4. 文件持久化存储
    • 使用move_uploaded_file()将文件移至安全目录。
    • 建议存储路径与Web根目录分离(如/var/uploads/)。

六、高级话题

1. 大文件上传优化
  • 调整配置

    upload_max_filesize = 2G
    post_max_size = 2G
    max_execution_time = 3600
    
  • 分片上传:通过JavaScript实现文件分片,服务端重组。

2. 异步上传
  • 使用AJAX + FormData对象实现无刷新上传:

    let formData = new FormData();
    formData.append('file', fileInput.files[0]);
    fetch('/upload.php', { method: 'POST', body: formData });
    
3. 防御0day漏洞
  • 禁用危险函数:确保上传目录不可执行PHP代码。
  • 内容二次渲染:对图片文件进行GD库处理,破坏潜在恶意代码。

七、错误处理深度解析

  • 自定义错误消息

    $phpFileUploadErrors = [0 => '成功',1 => '文件超过php.ini限制',2 => '文件超过表单限制',3 => '文件仅部分上传',4 => '未选择文件',6 => '缺少临时文件夹',7 => '写入磁盘失败',8 => 'PHP扩展阻止了上传',
    ];
    
  • 错误触发场景

    • UPLOAD_ERR_INI_SIZE:文件大小超过upload_max_filesize
    • UPLOAD_ERR_PARTIAL:网络中断导致上传不完整。

二,表单制作

一、基础表单结构

1.必要属性配置
<form action="upload.php" method="POST" enctype="multipart/form-data"><input type="file" name="userfile" required><input type="submit" value="上传">
</form>

关键要素

  • method="POST":必须使用POST方法传输文件
  • enctype="multipart/form-data":启用二进制流传输模式
  • required:HTML5客户端必填验证
2.多文件上传支持
<input type="file" name="files[]" multiple accept=".jpg,.png">

特性说明

  • multiple:允许选择多个文件
  • accept:限制可选文件类型(客户端过滤)

二、高级表单功能

1. 文件类型限制
<!-- 仅允许图片文件 -->
<input type="file" accept="image/*"><!-- 指定具体扩展名 -->
<input type="file" accept=".pdf,.doc,.docx">
2. 文件大小提示
<input type="file" onchange="checkSize(this)">
<script>
function checkSize(input) {const maxSize = 2 * 1024 * 1024; // 2MBif (input.files[0].size > maxSize) {alert('文件大小超过限制');input.value = ''; // 清空选择}
}
</script>
3. 拖拽上传实现
<div id="drop-zone" style="border:2px dashed #ccc; padding:20px;">拖拽文件至此区域
</div><script>
const dropZone = document.getElementById('drop-zone');dropZone.addEventListener('dragover', (e) => {e.preventDefault();dropZone.style.borderColor = '#666';
});dropZone.addEventListener('drop', (e) => {e.preventDefault();const files = e.dataTransfer.files;// 处理文件上传逻辑
});
</script>

三、安全增强配置

1. 隐藏域Token验证
<input type="hidden" name="csrf_token" value="<?= $_SESSION['token'] ?>">

后端验证

if ($_POST['csrf_token'] !== $_SESSION['token']) {die("非法请求");
}
2. 文件名过滤处理
// 删除特殊字符
$cleanName = preg_replace("/[^\w\.]/", '', $_FILES['file']['name']);
// 防止覆盖
$filename = uniqid().'_'.$cleanName;

三,$_FILES变量

一、$_FILES变量基础结构

$_FILES是PHP自动生成的超全局数组,用于存储通过HTTP POST上传的文件信息。其结构为多维数组,典型结构如下:

$_FILES = ['file_field_name' => ['name'     => 'example.jpg',    // 客户端原始文件名'type'     => 'image/jpeg',     // 浏览器报告的MIME类型'tmp_name' => '/tmp/php3h4j8h', // 服务器上的临时文件路径'error'    => 0,                // 上传错误代码'size'     => 102400            // 文件大小(字节)]
];

二、核心字段深度解析

1. name字段
  • 来源:客户端文件系统原始名称

  • 风险:可能包含特殊字符或路径信息(如../../shell.php

  • 安全处理

    // 过滤非法字符并提取安全文件名
    $safe_name = basename($_FILES['file']['name']);
    $clean_name = preg_replace('/[^\w\.-]/', '', $safe_name);
    
2. type字段
  • 来源:浏览器根据文件扩展名猜测的类型

  • 可靠性:极易伪造(如将.exe文件重命名为.jpg)

  • 验证方法

    $finfo = finfo_open(FILEINFO_MIME_TYPE);
    $real_mime = finfo_file($finfo, $_FILES['file']['tmp_name']);
    finfo_close($finfo);
    
3. tmp_name字段
  • 特性
    • 临时文件路径由php.iniupload_tmp_dir配置决定
    • 文件命名规则为phpXXXXXX(X为随机字符)
  • 生命周期
    • 脚本执行结束后自动删除
    • 必须使用move_uploaded_file()转移文件
4. error字段
  • 错误代码对照表

    常量说明
    UPLOAD_ERR_OK0上传成功
    UPLOAD_ERR_INI_SIZE1超过php.ini大小限制
    UPLOAD_ERR_FORM_SIZE2超过表单MAX_FILE_SIZE值
    UPLOAD_ERR_PARTIAL3文件只有部分被上传
    UPLOAD_ERR_NO_FILE4没有文件被上传
    UPLOAD_ERR_NO_TMP_DIR6找不到临时文件夹
    UPLOAD_ERR_CANT_WRITE7文件写入失败
    UPLOAD_ERR_EXTENSION8PHP扩展阻止上传
  • 错误处理示例

    $error_messages = [0 => 'Success',1 => 'File exceeds php.ini upload_max_filesize',2 => 'File exceeds form MAX_FILE_SIZE',3 => 'Partial upload',4 => 'No file uploaded',6 => 'Missing temporary directory',7 => 'Failed to write to disk',8 => 'PHP extension blocked upload'
    ];if ($_FILES['file']['error'] !== UPLOAD_ERR_OK) {die($error_messages[$_FILES['file']['error']]);
    }
    
5. size字段
  • 单位:字节(1MB = 1,048,576字节)

  • 验证示例

    $max_size = 5 * 1024 * 1024; // 5MB
    if ($_FILES['file']['size'] > $max_size) {die("文件大小超过5MB限制");
    }
    

三、保存上传文件

1. is_uploaded_file()

核心作用

  • 验证指定文件是否通过HTTP POST上传
  • 防止伪造文件路径攻击

函数原型

bool is_uploaded_file(string $filename)

使用场景

// 验证临时文件合法性
if (!is_uploaded_file($_FILES['file']['tmp_name'])) {die("非法文件来源");
}

安全机制

  • 检查文件路径是否在upload_tmp_dir目录下
  • 验证文件名匹配PHP临时文件命名规则(如phpXXXXXX
  • 防止攻击者通过伪造路径访问系统文件

典型错误用法

// 错误:直接使用$_FILES中的原始名称
$tmp = '/tmp/' . $_FILES['file']['name']; 
if (file_exists($tmp)) { ... } // 存在路径注入风险
2. move_uploaded_file()

核心作用

  • 安全移动上传的临时文件到目标位置
  • 兼具is_uploaded_file()验证功能

函数原型

bool move_uploaded_file(string $from, string $to)

使用规范

$safe_dir = '/var/www/uploads/';
$new_name = uniqid() . '_' . basename($_FILES['file']['name']);if (move_uploaded_file($_FILES['file']['tmp_name'], $safe_dir . $new_name
)) {// 成功处理
} else {// 失败处理
}

安全特性

  1. 自动执行is_uploaded_file()验证
  2. 防止路径遍历攻击(自动处理../
  3. 原子操作:移动失败时不会残留部分文件

与普通移动函数的对比

特性move_uploaded_file()rename()/copy()
自动安全验证✔️
跨设备移动支持✔️
保持文件权限✔️
防止路径遍历✔️

双函数协作流程图

通过
失败
成功
失败
上传请求
is_uploaded_file验证
move_uploaded_file移动
终止处理
文件持久化
错误处理

最佳实践示例
function validateUpload($file) {// 错误检查if ($file['error'] !== UPLOAD_ERR_OK) return false;// 临时文件验证if (!is_uploaded_file($file['tmp_name'])) return false;// MIME类型检测$finfo = finfo_open(FILEINFO_MIME_TYPE);$mime = finfo_file($finfo, $file['tmp_name']);finfo_close($finfo);if (!in_array($mime, ['image/jpeg', 'image/png'])) return false;// 扩展名验证$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));if (!in_array($ext, ['jpg', 'jpeg', 'png'])) return false;// 文件大小限制if ($file['size'] > 5*1024*1024) return false;// 内容安全检查(示例:图片验证)$image = @imagecreatefromjpeg($file['tmp_name']);if (!$image) return false;imagedestroy($image);return true;
}

四、调试技巧

1. 打印完整结构
echo '<pre>' . print_r($_FILES, true) . '</pre>';
2. 临时文件检查
if (file_exists($_FILES['file']['tmp_name'])) {echo '临时文件大小: ' . filesize($_FILES['file']['tmp_name']);
} else {echo '临时文件已消失';
}
3. 上传限制检测
echo 'PHP最大上传: ' . ini_get('upload_max_filesize');
echo 'POST最大大小: ' . ini_get('post_max_size');
echo '临时目录: ' . sys_get_temp_dir();

五、常见问题解决

问题1:$_FILES数组为空

  • 检查php.inifile_uploads是否开启
  • 验证表单enctype="multipart/form-data"
  • 检查Web服务器配置(如Nginx的client_max_body_size

问题2:部分文件上传失败

  • 确认upload_tmp_dir有足够权限(至少755)
  • 检查磁盘空间是否充足
  • 监控max_file_uploads配置

问题3:大文件上传中断

  • 调整以下配置:

    upload_max_filesize = 256M
    post_max_size = 257M
    max_execution_time = 3600
    max_input_time = 3600
    memory_limit = 512M
    

总结

  1. 永远不要信任$_FILES中的客户端数据
  2. 必须进行双重验证(MIME类型+扩展名+内容检查)
  3. 使用move_uploaded_file()而非copy()rename()
  4. 上传目录设置为不可执行(chmod 755 uploads/
  5. 定期清理旧文件(通过cron作业)

四,多文件上传

一、前端表单设置

<form action="upload.php" method="post" enctype="multipart/form-data"><input type="file" name="user_files[]" multiple accept=".jpg,.png"><input type="submit" value="批量上传">
</form>

关键点

  • name="user_files[]":必须使用数组形式命名
  • multiple:启用多选支持(HTML5特性)
  • accept:限制可选文件类型(客户端过滤)

二、后端文件数据结构

1. 原生$_FILES结构
$_FILES = ['user_files' => ['name'     => ['a.jpg', 'b.png'],    // 文件名数组'type'     => ['image/jpeg', 'image/png'],  'tmp_name' => ['/tmp/phpX1', '/tmp/phpX2'],'error'    => [0, 0],                // 错误码数组'size'     => [102400, 204800]        // 大小数组]
];
2. 重组为易用格式
$files = [];
$fileCount = count($_FILES['user_files']['name']);for ($i = 0; $i < $fileCount; $i++) {$files[] = ['name'     => $_FILES['user_files']['name'][$i],'type'     => $_FILES['user_files']['type'][$i],'tmp_name' => $_FILES['user_files']['tmp_name'][$i],'error'    => $_FILES['user_files']['error'][$i],'size'     => $_FILES['user_files']['size'][$i]];
}

重组后结构

$files = [['name' => 'a.jpg','type' => 'image/jpeg','tmp_name' => '/tmp/phpX1','error' => 0,'size' => 102400],['name' => 'b.png','type' => 'image/png','tmp_name' => '/tmp/phpX2','error' => 0,'size' => 204800]
];

三、完整处理流程

1. 验证上传状态
if (empty($_FILES['user_files']['tmp_name'][0])) {die("未选择任何文件");
}
2. 遍历处理每个文件
$uploadResults = [];
$allowedTypes = ['image/jpeg', 'image/png'];
$maxFileSize = 2 * 1024 * 1024; // 2MB
$uploadDir = __DIR__ . '/uploads/';foreach ($files as $index => $file) {try {// 检查上传错误if ($file['error'] !== UPLOAD_ERR_OK) {throw new Exception("文件{$index}上传失败,错误码:{$file['error']}");}// 验证MIME类型$finfo = finfo_open(FILEINFO_MIME_TYPE);$realMime = finfo_file($finfo, $file['tmp_name']);finfo_close($finfo);if (!in_array($realMime, $allowedTypes)) {throw new Exception("文件{$index}类型不合法");}// 验证扩展名$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));if (!in_array($ext, ['jpg', 'jpeg', 'png'])) {throw new Exception("文件{$index}扩展名不合法");}// 验证大小if ($file['size'] > $maxFileSize) {throw new Exception("文件{$index}超过大小限制");}// 生成唯一文件名$safeName = md5(uniqid() . $file['name']) . '.' . $ext;$targetPath = $uploadDir . $safeName;// 移动文件if (!move_uploaded_file($file['tmp_name'], $targetPath)) {throw new Exception("文件{$index}保存失败");}$uploadResults[] = ['original' => $file['name'],'saved_as' => $safeName,'status' => 'success'];} catch (Exception $e) {$uploadResults[] = ['original' => $file['name'],'error' => $e->getMessage(),'status' => 'failed'];}
}
3. 输出结果
echo json_encode(['total' => count($files),'success' => count(array_filter($uploadResults, fn($item) => $item['status'] === 'success')),'results' => $uploadResults
]);

四、高级处理技巧

1. 并发上传优化
// 使用PHP的并行处理扩展(需安装parallel)
$parallel = new \parallel\Runtime();
$futures = [];foreach ($files as $file) {$futures[] = $parallel->run(function($file) {// 文件处理逻辑}, [$file]);
}// 收集结果
$results = array_map(fn($f) => $f->value(), $futures);
2. 进度监控实现
// 前端JavaScript
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', e => {const percent = Math.round((e.loaded / e.total) * 100);progressBar.style.width = percent + '%';
});
3. 断点续传支持

实现步骤

  1. 前端分片文件(使用Blob.slice())
  2. 服务端记录已接收分片
  3. 合并分片文件:
// 合并示例
$totalChunks = 5;
$finalFile = 'merged_file.zip';for ($i=1; $i<=$totalChunks; $i++) {$chunk = file_get_contents("chunk_{$i}.part");file_put_contents($finalFile, $chunk, FILE_APPEND);
}

五、安全防护策略

1. 防御DDoS攻击
// 限制并发上传数量
if (count($files) > 10) {http_response_code(429);die("一次最多上传10个文件");
}
2. 病毒扫描集成
// 使用ClamAV扫描
$clamscan = '/usr/bin/clamscan';
$output = shell_exec("$clamscan --no-summary $targetPath");
if (strpos($output, 'OK') === false) {unlink($targetPath);throw new Exception("文件感染病毒");
}
3. 敏感内容检测
// 检查图片是否包含裸露内容(示例使用NSFW.js)
$imageData = file_get_contents($targetPath);
$nsfwCheck = shell_exec("node nsfw-check.js $imageData");
if ($nsfwCheck > 0.7) {unlink($targetPath);throw new Exception("检测到违规内容");
}

六、服务器配置优化

php.ini关键参数
; 允许同时上传的文件数
max_file_uploads = 20; 单个文件最大尺寸
upload_max_filesize = 50M; POST数据最大尺寸
post_max_size = 55M; 脚本最大执行时间
max_execution_time = 1800
Nginx配置示例
client_max_body_size 55M;
client_body_temp_path /var/nginx/client_temp;
client_body_in_file_only clean;

七、错误排查指南

现象可能原因解决方案
$_FILES数组为空表单未设置enctype检查表单enctype属性
部分文件上传失败临时目录权限不足chmod 755 /tmp
文件名乱码编码不一致使用mb_convert_encoding转换
大文件上传中断超时设置过小调整max_execution_time
无法生成缩略图GD库未安装安装php-gd扩展

八、完整类封装示例

class MultiFileUploader {private $uploadDir;private $allowedMimes;private $maxSize;public function __construct($uploadDir, $allowedMimes, $maxSize) {$this->uploadDir = rtrim($uploadDir, '/') . '/';$this->allowedMimes = $allowedMimes;$this->maxSize = $maxSize;$this->createUploadDir();}private function createUploadDir() {if (!is_dir($this->uploadDir)) {mkdir($this->uploadDir, 0755, true);}}public function process($fileField) {$files = $this->reorganizeFiles($_FILES[$fileField]);$results = [];foreach ($files as $file) {try {$this->validateFile($file);$filename = $this->generateFilename($file);$this->moveFile($file['tmp_name'], $filename);$results[] = $this->successResult($file, $filename);} catch (Exception $e) {$results[] = $this->errorResult($file, $e);}}return $results;}private function reorganizeFiles($files) {$organized = [];foreach ($files as $key => $values) {foreach ($values as $index => $value) {$organized[$index][$key] = $value;}}return $organized;}// ...其他方法实现...
}// 使用示例
$uploader = new MultiFileUploader(__DIR__ . '/uploads',['image/jpeg', 'image/png'],2 * 1024 * 1024
);
$results = $uploader->process('user_files');

五,函数封装


一、函数封装理论体系

1. 抽象层次模型
HTTP协议层
PHP运行时层
安全验证层
业务逻辑层
持久化层
  • 协议抽象:封装multipart/form-data解析细节
  • 资源管理:统一处理临时文件生命周期
  • 正交设计:验证逻辑与存储逻辑解耦
2. 设计模式应用
  • 策略模式:可插拔的验证规则(MIME检测策略、病毒扫描策略)
  • 工厂模式:根据文件类型创建不同的处理器(图片处理器、文档处理器)
  • 装饰器模式:动态添加功能(日志记录、内容过滤)
  • 观察者模式:实现上传进度通知机制
3. SOLID原则映射
原则实现方式
单一职责分离验证、存储、后处理模块
开闭原则通过继承扩展功能而非修改源码
里氏替换子类处理器保持父类接口兼容
接口隔离定义UploadValidator独立接口
依赖倒置依赖抽象接口而非具体实现

二、核心技术实现

1. 安全防御技术栈
输入消毒
类型验证
内容扫描
权限控制
审计追踪
  • 深度防御模型

    1. 文件名消毒:正则过滤/[^a-z0-9\-_.]/i
    2. 双验证机制:文件签名+MIME类型
    3. 沙箱检测:使用QEMU虚拟环境执行可疑文件
    4. 权限最小化:上传目录chmod 755 + open_basedir限制
  • 零信任实现

    class ZeroTrustValidator {public function validate($file) {$this->checkOrigin($file['tmp_name']);$this->verifySignature($file['tmp_name']);$this->analyzeEntropy($file['tmp_name']);}private function checkOrigin($path) {if (!is_uploaded_file($path)) {throw new SecurityException("非法文件来源");}}
    }
    
2. 异步处理架构
Client API_Gateway Message_Queue Upload_Worker 发起上传请求 存入任务队列 分发处理任务 回调通知结果 Client API_Gateway Message_Queue Upload_Worker
  • 分片上传算法

    def upload_chunk(file, chunk_size=5*1024*1024):total = math.ceil(file.size / chunk_size)for i in range(total):chunk = file.read(chunk_size)hash = sha256(chunk).hexdigest()redis.set(f"upload:{file.id}:{i}", {'hash': hash,'data': base64.b64encode(chunk)})return merge_chunks(file.id, total)
    
3. 可观测性设计
  • 指标收集

    # TYPE file_upload_size histogram
    file_upload_size_bucket{status="success",le="1048576"} 42
    file_upload_size_bucket{status="success",le="5242880"} 87# TYPE upload_error_counter counter
    upload_error_counter{type="size_limit"} 3
    
  • 分布式追踪

    {"trace_id": "abc123","span_id": "def456","operation": "FileUpload","tags": {"file.size": "2.4MB","validation.time": "128ms"}
    }
    

三,FileUploader 类设计

<?php
/*** 安全文件上传处理器* * 功能特性:* 1. 多文件上传支持* 2. MIME类型白名单验证* 3. 文件扩展名过滤* 4. 自动生成安全文件名* 5. 病毒扫描集成接口* 6. 图片EXIF信息处理* 7. 上传进度跟踪* 8. 自动目录创建* 9. 防御性错误处理*/
class FileUploader {// 配置参数private $config = ['upload_dir'      => __DIR__.'/uploads', // 上传目录'allowed_mimes'   => [],                // 允许的MIME类型'allowed_exts'    => [],                // 允许的扩展名'max_size'        => 2 * 1024 * 1024,   // 最大文件尺寸(2MB)'overwrite'       => false,             // 是否覆盖同名文件'sanitize_name'   => true,              // 自动清理文件名'hash_name'       => true,              // 使用哈希文件名'virus_scan'      => false,             // 启用病毒扫描'image_handling'  => [                  // 图片处理配置'resize' => ['enabled' => false,'width'   => 800,'height'  => 600],'strip_exif' => true]];// 运行时状态private $errors = [];private $uploadedFiles = [];/*** 构造函数* @param array $config 自定义配置项*/public function __construct(array $config = []) {$this->config = array_merge($this->config, $config);$this->init();}/*** 初始化验证*/private function init() {// 检查上传功能是否启用if (!ini_get('file_uploads')) {throw new RuntimeException('服务器未启用文件上传功能');}// 创建上传目录if (!is_dir($this->config['upload_dir'])) {$this->createDirectory($this->config['upload_dir']);}// 验证目录可写if (!is_writable($this->config['upload_dir'])) {throw new RuntimeException('上传目录不可写: '.$this->config['upload_dir']);}}/*** 处理文件上传* @param string $fieldName 表单字段名* @return array 上传结果*/public function upload(string $fieldName): array {$this->resetState();if (!isset($_FILES[$fieldName])) {$this->errors[] = "未找到上传字段: {$fieldName}";return $this->getResult();}$files = $this->reorganizeFiles($_FILES[$fieldName]);foreach ($files as $file) {$this->processSingleFile($file);}return $this->getResult();}/*** 重组多文件数组结构*/private function reorganizeFiles(array $files): array {$organized = [];foreach ($files as $key => $values) {foreach ($values as $index => $value) {$organized[$index][$key] = $value;}}return $organized;}/*** 处理单个文件*/private function processSingleFile(array $file) {try {// 基础验证$this->validateBasic($file);// 安全验证$this->validateSecurity($file);// 生成目标路径$destination = $this->generateDestination($file);// 移动文件$this->moveUploadedFile($file['tmp_name'], $destination);// 后处理$this->postProcess($destination, $file);// 记录成功$this->uploadedFiles[] = ['original_name' => $file['name'],'saved_path'    => $destination,'size'          => $file['size'],'mime_type'     => $this->getRealMimeType($file['tmp_name'])];} catch (Exception $e) {$this->errors[] = $file['name'].': '.$e->getMessage();}}/*** 基础验证*/private function validateBasic(array $file) {// 错误代码验证if ($file['error'] !== UPLOAD_ERR_OK) {throw new RuntimeException($this->getUploadError($file['error']));}// 临时文件验证if (!is_uploaded_file($file['tmp_name'])) {throw new RuntimeException('非法文件来源');}// 文件大小验证if ($file['size'] > $this->config['max_size']) {$maxSize = round($this->config['max_size'] / 1024 / 1024, 1);throw new RuntimeException("文件超过 {$maxSize}MB 限制");}}/*** 安全验证*/private function validateSecurity(array $file) {// MIME类型验证$realMime = $this->getRealMimeType($file['tmp_name']);if (!in_array($realMime, $this->config['allowed_mimes'])) {throw new RuntimeException("禁止的文件类型: {$realMime}");}// 扩展名验证$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));if (!in_array($ext, $this->config['allowed_exts'])) {throw new RuntimeException("禁止的文件扩展名: .{$ext}");}// 病毒扫描if ($this->config['virus_scan']) {$this->scanForVirus($file['tmp_name']);}}/*** 获取真实MIME类型*/private function getRealMimeType(string $tmpPath): string {$finfo = finfo_open(FILEINFO_MIME_TYPE);$mime = finfo_file($finfo, $tmpPath);finfo_close($finfo);return $mime;}/*** 生成目标路径*/private function generateDestination(array $file): string {$filename = $this->config['sanitize_name'] ? $this->sanitizeFilename($file['name']): $file['name'];if ($this->config['hash_name']) {$ext = pathinfo($filename, PATHINFO_EXTENSION);$filename = md5(uniqid().microtime()).'.'.$ext;}$destination = $this->config['upload_dir'].DIRECTORY_SEPARATOR.$filename;// 防覆盖处理if (!$this->config['overwrite'] && file_exists($destination)) {throw new RuntimeException('文件已存在');}return $destination;}/*** 安全移动文件*/private function moveUploadedFile(string $tmpPath, string $destination) {if (!move_uploaded_file($tmpPath, $destination)) {throw new RuntimeException('文件保存失败');}// 设置安全权限chmod($destination, 0644);}/*** 文件名消毒*/private function sanitizeFilename(string $filename): string {// 删除路径信息$clean = basename($filename);// 替换特殊字符$clean = preg_replace("/[^a-zA-Z0-9\-_.]/", '_', $clean);// 缩短长度return substr($clean, 0, 200);}/*** 病毒扫描*/private function scanForVirus(string $filePath) {// 示例:集成ClamAV$output = shell_exec("clamscan --no-summary {$filePath}");if (strpos($output, 'OK') === false) {unlink($filePath);throw new RuntimeException('文件包含病毒或恶意代码');}}/*** 上传后处理*/private function postProcess(string $filePath, array $originalFile) {// 图片处理if (strpos($originalFile['type'], 'image/') === 0) {$this->processImage($filePath);}}/*** 图片处理*/private function processImage(string $filePath) {try {// 去除EXIF信息if ($this->config['image_handling']['strip_exif']) {$this->stripExif($filePath);}// 调整尺寸if ($this->config['image_handling']['resize']['enabled']) {$this->resizeImage($filePath,$this->config['image_handling']['resize']['width'],$this->config['image_handling']['resize']['height']);}} catch (Exception $e) {unlink($filePath);throw new RuntimeException('图片处理失败: '.$e->getMessage());}}// ...其他辅助方法.../*** 获取最终结果*/public function getResult(): array {return ['success' => $this->uploadedFiles,'errors'  => $this->errors,'total'   => count($this->uploadedFiles) + count($this->errors),'passed'  => count($this->uploadedFiles),'failed'  => count($this->errors)];}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/78711.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CSS元素动画篇:基于当前位置的变换动画(三)

基于当前位置的变换动画&#xff08;三&#xff09; 前言缩放效果类元素动画脉冲动画效果效果预览代码实现 橡皮筋动画效果效果预览代码实现 果冻动画效果效果预览代码实现 欢呼动画效果效果预览代码实现 心跳动画效果效果预览代码实现 结语 前言 CSS元素动画一般分为两种&…

Redis ssd是什么?Redis 内存空间优化的点都有哪些?embstr 和 row、intset、ziplist分别是什么?

Redis SSD 是什么&#xff1f; Redis SSD 通常指 Redis 使用 SSD&#xff08;固态硬盘&#xff09;作为持久化存储介质的场景。虽然 Redis 是内存数据库&#xff08;数据主要驻留内存&#xff09;&#xff0c;但其持久化机制&#xff08;如 RDB 快照和 AOF 日志&#xff09;需…

【蓝桥杯】 数字诗意

数字诗意 在诗人的眼中&#xff0c;数字是生活的韵律&#xff0c;也是诗意的表达。 小蓝&#xff0c;当代顶级诗人与数学家&#xff0c;被赋予了”数学诗人”的美誉。他擅长将冰冷的数字与抽象的诗意相融合&#xff0c;并用优雅的文字将数学之美展现于纸上。 某日&#xff0…

DHCP 服务器运行流程图

以常见的 DHCP v4 为例,其完整流程如下: 一、客户端请求 IP 地址阶段 DHCPDiscover:客户端启动后,会以广播的形式发送 DHCPDiscover 报文,目的是在网络中寻找可用的 DHCP 服务器。该报文中包含客户端的 MAC 地址等信息,以便服务器能够识别客户端。DHCPOffer:网络中的 D…

一种企业信息查询系统设计和实现:xujian.tech/cs

一种企业信息查询系统设计和实现&#xff1a;xujian.tech/cs 背景与定位 企业在对外合作、风控审查或市场调研时&#xff0c;常需快速获取公开的工商信息。本文介绍一个企业信息搜索引擎&#xff0c;面向普通用户与开发者&#xff0c;帮助快速定位企业名称、统一社会信用代码…

前端面试高频算法

前端面试高频算法 1 排序算法&#xff1b;1.1 如何分析一个排序算法1.1.1 执行效率3.1.2 内存消耗1.1.3 稳定性 1.2 冒泡排序&#xff08;Bubble Sort&#xff09;1.3 插入排序&#xff08;Insertion Sort&#xff09;1.4 选择排序&#xff08;Selection Sort&#xff09;1.5 归…

C++初阶-模板初阶

目录 1.泛型编程 2.函数模板 2.1函数模板概念 2.2实现函数模板 2.3模板的原理 2.4函数模板的实例化 2.4.1隐式实例化 2.4.2显式初始化 2.5模板参数的匹配原则 3.类模板 3.1类模板定义格式 3.2类模板的实例化 4.总结 1.泛型编程 对广泛的类型法写代码&#xff0c;我…

「Mac畅玩AIGC与多模态02」部署篇01 - 在 Mac 上部署 Ollama + Open WebUI

一、概述 本篇介绍如何在 macOS 环境下本地部署 Ollama 推理服务,并通过 Open WebUI 实现可视化交互界面。该流程无需 CUDA 或专用驱动,适用于 M 系列或 Intel 芯片的 Mac,便于快速测试本地大语言模型能力。 二、部署流程 1. 环境准备 安装 Homebrew(如尚未安装):/bin…

JavaScript 中 undefined 和 not defined 的区别

在 JavaScript 的调试过程中&#xff0c;你是否经常看到 undefined 却不知其来源&#xff1f;是否曾被 ReferenceError: xxx is not defined 的错误提示困扰&#xff1f;这两个看似相似的概念&#xff0c;实际上是 JavaScript 类型系统中最重要的分水岭。本文将带你拨开迷雾&am…

django admin AttributeError: ‘UserResorce‘ object has no attribute ‘ID‘

在 Django 中遇到 AttributeError: ‘UserResource’ object has no attribute ‘ID’ 这类错误通常是因为你在代码中尝试访问一个不存在的属性。在你的例子中&#xff0c;错误提示表明 UserResource 类中没有名为 ID 的属性。这可能是由以下几个原因造成的&#xff1a; 拼写错…

对鸿蒙 Next 系统“成熟论”的深度剖析-优雅草卓伊凡

对鸿蒙 Next 系统“成熟论”的深度剖析-优雅草卓伊凡 在科技飞速发展的当下&#xff0c;鸿蒙 Next 系统无疑成为了众多科技爱好者与行业人士关注的焦点。今日&#xff0c;卓伊凡便收到这样一个饶有趣味的问题&#xff1a;鸿蒙 Next 系统究竟需要多长时间才能完全成熟&#xff…

快速上手GO的net/http包,个人学习笔记

更多个人笔记&#xff1a;&#xff08;仅供参考&#xff0c;非盈利&#xff09; gitee&#xff1a; https://gitee.com/harryhack/it_note github&#xff1a; https://github.com/ZHLOVEYY/IT_note 针对GO中net/http包的学习笔记 基础快速了解 创建简单的GOHTTP服务 func …

AI-Browser适用于 ChatGPT、Gemini、Claude、DeepSeek、Grok的客户端开源应用程序,集成了 Monaco 编辑器。

一、软件介绍 文末提供程序和源码下载学习 AI-Browser适用于 ChatGPT、Gemini、Claude、DeepSeek、Grok、Felo、Cody、JENOVA、Phind、Perplexity、Genspark 和 Google AI Studio 的客户端应用程序&#xff0c;集成了 Monaco 编辑器。使用 Electron 构建的强大桌面应用程序&a…

Dify框架面试内容整理-Dify如何处理知识库的集成?

Dify 在知识库集成方面采用了“检索增强生成(RAG)”的技术架构,核心实现思路如下: 一、知识库集成的整体流程 Dify处理知识库集成通常包括以下关键步骤: 文档上传↓

Laravel 模型使用全局作用域和局部作用域

一. 需要解决什么问题 最近Laravel 项目中遇到一个需求&#xff0c;我有一个客户表&#xff0c;每个员工都有自己的客户&#xff0c;但是自己只能看自己的客户。 项目中&#xff0c;有很多功能需要查询客户列表&#xff0c;客户详情&#xff0c;查询客户入口很多&#xff0c;…

【Nova UI】十二、打造组件库之按钮组件(上):迈向功能构建的关键一步

序言 在上一篇文章中&#xff0c;我们深入探索了 icon 组件从测试到全局注册的全过程&#x1f3af;&#xff0c;成功为其在项目中稳定运行筑牢了根基。此刻&#xff0c;组件库的建设之旅仍在继续&#xff0c;我们将目光聚焦于另一个关键组件 —— 按钮组件。按钮作为用户与界面…

鸿蒙OSS文件(视频/图片)压缩上传组件-能够增删改查

一、鸿蒙实现处理-压缩上传整体代码处理逻辑 转沙箱压缩获取凭证并上传文件 文件准备&#xff08;拿到文件流&#xff09;获取上传凭证&#xff08;调接口1拿到file_name和upload_url&#xff09;执行文件上传&#xff08;向阶段2拿到的upload_url上传文件&#xff09;更新列表…

河道流量监测,雷达流量计赋能水安全智慧守护

在蜿蜒的河道之上&#xff0c;水流的脉搏始终与人类文明的兴衰紧密相连。从农田灌溉的水量调配到城市防洪的精准预警&#xff0c;从生态保护的水质溯源到水资源管理的决策&#xff0c;河道流量监测如同大地的 “血管检测”&#xff0c;是守护水安全的第一道防线。传统监测手段在…

CSS3 基础(边框效果)

一、边框效果 属性功能示例值说明border-radius创建圆角border-radius: 20px;设置元素的圆角半径&#xff0c;支持像素&#xff08;px&#xff09;或百分比&#xff08;%&#xff09;。值为 50% 时可变为圆形。box-shadow添加阴影box-shadow: 5px 5px 15px rgba(0, 0, 0, 0.5)…

零基础小白如何上岸数模国奖

零基础小白如何上岸数模国奖 我自己本人第一次参加数模国赛顺利上岸国奖&#xff0c;当然那段经历也是比较痛苦了&#xff0c;差不多也是从当年四月开始接触数学建模&#xff0c;第一次参加妈妈杯成绩并不理想&#xff0c;后面不断参加数模比赛进行模拟&#xff0c;最后顺利上岸…