第十二章-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.ini
的upload_tmp_dir
指定。 - 临时文件名随机生成(如
/tmp/phpA3b4cD
),与原始文件名无关。
- PHP将上传的文件内容写入临时目录(
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_uploads | On | 是否允许HTTP文件上传 |
upload_max_filesize | 2M | 单个文件最大大小 |
post_max_size | 8M | POST请求最大数据量(必须大于上传限制) |
upload_tmp_dir | 系统临时目录 | 临时文件存储路径 |
max_file_uploads | 20 | 单次请求允许上传的最大文件数 |
配置关系:
post_max_size >= upload_max_filesize * max_file_uploads
五、完整上传流程
- 客户端提交表单:
- 浏览器将文件编码为
multipart/form-data
格式。 - 分块传输至服务器(对用户透明)。
- 浏览器将文件编码为
- 服务端接收数据:
- Web服务器(如Nginx/Apache)接收原始数据流。
- PHP SAPI解析请求体,生成临时文件。
- PHP脚本处理:
- 访问
$_FILES
获取文件信息。 - 执行错误检查、安全验证、文件移动。
- 访问
- 文件持久化存储:
- 使用
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.ini
的upload_tmp_dir
配置决定 - 文件命名规则为
phpXXXXXX
(X为随机字符)
- 临时文件路径由
- 生命周期:
- 脚本执行结束后自动删除
- 必须使用
move_uploaded_file()
转移文件
4. error
字段
-
错误代码对照表:
常量 值 说明 UPLOAD_ERR_OK
0 上传成功 UPLOAD_ERR_INI_SIZE
1 超过php.ini大小限制 UPLOAD_ERR_FORM_SIZE
2 超过表单MAX_FILE_SIZE值 UPLOAD_ERR_PARTIAL
3 文件只有部分被上传 UPLOAD_ERR_NO_FILE
4 没有文件被上传 UPLOAD_ERR_NO_TMP_DIR
6 找不到临时文件夹 UPLOAD_ERR_CANT_WRITE
7 文件写入失败 UPLOAD_ERR_EXTENSION
8 PHP扩展阻止上传 -
错误处理示例:
$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 {// 失败处理
}
安全特性
- 自动执行
is_uploaded_file()
验证 - 防止路径遍历攻击(自动处理
../
) - 原子操作:移动失败时不会残留部分文件
与普通移动函数的对比
特性 | move_uploaded_file() | rename() /copy() |
---|---|---|
自动安全验证 | ✔️ | ❌ |
跨设备移动支持 | ❌ | ✔️ |
保持文件权限 | ❌ | ✔️ |
防止路径遍历 | ✔️ | ❌ |
双函数协作流程图
最佳实践示例
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.ini
的file_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
总结
- 永远不要信任
$_FILES
中的客户端数据 - 必须进行双重验证(MIME类型+扩展名+内容检查)
- 使用
move_uploaded_file()
而非copy()
或rename()
- 上传目录设置为不可执行(
chmod 755 uploads/
) - 定期清理旧文件(通过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. 断点续传支持
实现步骤:
- 前端分片文件(使用Blob.slice())
- 服务端记录已接收分片
- 合并分片文件:
// 合并示例
$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. 抽象层次模型
- 协议抽象:封装multipart/form-data解析细节
- 资源管理:统一处理临时文件生命周期
- 正交设计:验证逻辑与存储逻辑解耦
2. 设计模式应用
- 策略模式:可插拔的验证规则(MIME检测策略、病毒扫描策略)
- 工厂模式:根据文件类型创建不同的处理器(图片处理器、文档处理器)
- 装饰器模式:动态添加功能(日志记录、内容过滤)
- 观察者模式:实现上传进度通知机制
3. SOLID原则映射
原则 | 实现方式 |
---|---|
单一职责 | 分离验证、存储、后处理模块 |
开闭原则 | 通过继承扩展功能而非修改源码 |
里氏替换 | 子类处理器保持父类接口兼容 |
接口隔离 | 定义UploadValidator独立接口 |
依赖倒置 | 依赖抽象接口而非具体实现 |
二、核心技术实现
1. 安全防御技术栈
-
深度防御模型:
- 文件名消毒:正则过滤
/[^a-z0-9\-_.]/i
- 双验证机制:文件签名+MIME类型
- 沙箱检测:使用QEMU虚拟环境执行可疑文件
- 权限最小化:上传目录
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. 异步处理架构
-
分片上传算法:
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)];}
}
相关文章:
第十二章-PHP文件上传
第十二章-PHP文件上传 一,文件上传原理 一、HTTP协议与文件上传 1. 请求体结构 当表单设置enctype"multipart/form-data"时,浏览器会将表单数据编码为多部分(multipart)格式。 Boundary分隔符:随机生成的…...
shell脚本部署disu博客
#!/bin/bash #关闭防火墙 systemctl status firewalld &>/dev/null if [ $? -ne 0 ];then systemctl stop firewalld &>/dev/null else echo “firewalld is disabled” fi #关闭selinux filegetenforce if [ “$fine” “Disabled” ];then echo “firewalld…...
NdrpPointerUnmarshallInternal函数分析之pFormatPointee指针的确定
第一部分: 0: kd> p RPCRT4!NdrPointerUnmarshall0x29: 001b:77c46ce4 e8b6f6ffff call RPCRT4!NdrpPointerUnmarshall (77c4639f) 0: kd> t Breakpoint 4 hit RPCRT4!NdrpPointerUnmarshall: 001b:77c4639f 55 push ebp 0: kd> …...
STM32(M4)入门:定时器延时与系统滴答(价值 3w + 的嵌入式开发指南)
第 1 章 延时:嵌入式系统的时间控制基石 1.1 延时基础:从概念到硬件实现 1.1.1 什么是延时? 定义:延时是通过软件或硬件手段,使程序执行过程中暂停指定时间,再继续后续操作的技术。本质是对时间的精确或…...
2025 FIC wp
这次比赛计算机和手机大部分题目都比较常规 第一和第四部分有点让人摸不着头脑 比赛的时候第一部分有四个题没出 第四部分基本都没怎么出 现在复盘一下 把我当时做题的心得和获取的新知识记录一下 互联网取证的部分就先学习一下别的师傅 检材 链接:https://pan.bai…...
STM32标准库和HAL库SPI发送数据的区别-即SPI_I2S_SendData()和HAL_SPI_Transmit()互换
1、标准库SPI初始化 这是标准库的SPI初始化配置 2、HAL库SPI初始化 这是HAL库函数的SPI初始化配置 可以看出,基本一直,除了 基本的io口配置区别,其他主要的读写函数不用动的。 3、SPI发送函数_替换对比 /* SPI写入一个字节 */ void SP…...
Cesium 三维场景中通过自定义着色器实现多种特效(纹理、光带、点光源、反射)
整体功能概述 构建一个基于 Cesium 的三维场景,加载三维瓦片集模型,同时提供多种特效,像夜景纹理、上下扫光、点光源以及反射纹理等,利用 dat.gui 库创建交互界面。 代码详解 白膜特效 nightFs.glsl void fragmentMain(Fragm…...
Java学习--HashMap
HaspMap是Java集合框架中最重要、最常用的数据结构之一。其基于哈希表实现了Map接口,在Java1.8的版本中,其采用了“数组链表红黑树”的混合结构,底层代码如下: transient Node<K,V>[] table; // 哈希桶数组 static class N…...
Monorepo、Lerna、Yarn Workspaces、pnpm Workspaces 用法
Monorepo 介绍 Monorepo是一种方案,而非具体的工具。 Monorepo指的是将多个相关的项目或模块放在同一个代码仓库中进行管理的方式。这种方案有以下优点: 方便代码共享:不同项目或模块之间可以方便地共享代码、组件、工具函数等,…...
JVM指令手册:深入理解字节码执行机制
精心整理了最新的面试资料和简历模板,有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 引言 Java虚拟机(JVM)作为Java生态的核心执行引擎,其指令系统是理解程序底层运行机制的关键。本手册将系统解析JVM指令集…...
springboot logback 默认加载配置文件顺序
在 Spring Boot 应用中,Logback 默认加载配置文件的顺序遵循特定的规则。以下是详细的加载顺序和优先级说明: 1. 默认配置文件加载顺序 Logback 在 Spring Boot 中会按以下顺序查找并加载配置文件(优先级从高到低): l…...
用 Nodemon 解决 npm run serve 频繁重启服务
Nodemon 是一个基于 Node.js 构建的开发工具,专为帮助开发者自动监控项目文件的更改而设计。每当文件发生变更时,Nodemon 会自动重启 Node.js 服务器,无需手动停止并重启。这对于提升开发速度、减少人工操作非常有帮助,尤其适用于…...
WEB安全--社会工程--SET钓鱼网站
1、选择要钓鱼的网站 2、打开kali中的set 3、启动后依次选择: 4、输入钓鱼主机的地址(kali)和要伪装的网站域名: 5、投放钓鱼网页(服务器域名:80) 6、获取账号密码...
系统架构师---基于规则的系统架构
引言 在业务规则高度动态且需快速响应的系统中,基于规则的系统架构风格(Rule-Based System Architecture Style)提供了一种将业务逻辑与代码解耦的标准化范式。从保险理赔的自动化审核到金融风控的实时拦截,规则引擎已成为企…...
嵌入式软件--stm32 DAY 4 中断系统
1.课后练习 学了这么长时间,现在让我们第一次做练习。 1.1往返流水灯 1.1.1 LED1-LED2-LED3-LED2-LED1循环 (1)工程准备 复制上一个寄存器实现的工程文档,删减修改我们正要实现的工程。为了区别练习和学习工程,我们…...
android开发制作aosp系统签名文件给普通apk签名使用
platform.pk8和platform.x509.pem复制出来放在同一目录下 将AOSP源码路径下build\target\product\security\platform.pk8和platform.x509.pem复制出来放在同一目录下 新开一个ternimal窗口执行下面命令,生成platform.pem文件 openssl pkcs8 -in platform.pk8 -info…...
AVL树的介绍与学习
目录 1.前言 2.AVL树 3.AVL树的插入 平衡因子的更新 更新停止的条件 旋转 1.前言 在学习了二叉搜索树,set和map之后,我们接下来趁热打铁,继续学习AVL树。 2.AVL树 1.AVL树具有二叉搜索树的性质,但是它的左右子树的高度差不…...
docker部署Mysql8一直密码错误记录
正常流程是这样得: 第一步 #拉镜像 docker pull mysql:8.0 第二步 #运行名为 mysql8 得容器 ,MYSQL_ROOT_PASSWORD123456 设置密码 docker run -p 3307:3306 \ --name mysql8 \ -e MYSQL_ROOT_PASSWORD123456 \ -v /docker/mysql8/data:/var/lib/m…...
智慧水库与AI深度融合的实现方案及典型应用场景
以下是智慧水库与AI深度融合的实现方案及典型应用场景,结合行业前沿案例与技术架构展开: 一、智慧水库AI实现方案 1. 技术架构与核心工具 感知层: 多模态传感器网络:部署毫米波雷达水位计(精度3mm)、光纤光栅渗压计(分辨率0.01%FS)、高清智能球机(支持800万像素+AI分…...
大语言模型架构基础与挑战
大语言模型(Large Language Model, LLM)在近几年引领了自然语言处理领域的革命性进展。这类模型通常拥有极其庞大的参数规模(往往达到数十亿乃至数千亿级别),通过对海量文本数据进行自监督训练,展现出卓越的语言理解和生成能力。自2018年前后第一批大语言模型问世以来,基…...
KAG:通过知识增强生成提升专业领域的大型语言模型(二)
目录 摘要 Abstract 1 实验 1.1 实验设置 1.2 总体结果 1.3 消融研究 1.3.1 知识图谱索引消融 1.3.2 推理与检索消融 1.3.3 实验结果与讨论 2 KAG服务部署 2.1 安装Docker 2.2 安装Doker Compose 2.3 启动服务 2.4 查看状态 2.5 产品访问 3 KAG 0.6使用&#x…...
【Luogu】动态规划六
P1586 四方定理 - 洛谷 思路: 这题其实就是完全背包问题,但是有限制,最多数量只能是 4 所以我们可以定义 dp[i][j] 为 i 用 j 个数拼凑的总方案数 那么转移方程也很明显了,dp[i][j] dp[i - k*k][j - 1] 具体的,我…...
Postman接口测试: postman设置接口关联,实现参数化
🍅 点击文末小卡片,免费获取软件测试全套资料,资料在手,涨薪更快 postman设置接口关联 在实际的接口测试中,后一个接口经常需要用到前一个接口返回的结果, 从而让后一个接口能正常执行,这个…...
docker打开滚动日志
在 Docker 中启用滚动日志(log rotation)可以帮助你管理容器日志的大小,避免日志文件占用过多磁盘空间。以下是具体的操作步骤: 1. 修改 Docker 守护进程配置 Docker 的日志配置是通过 daemon.json 文件管理的。你需要修改此文件…...
单片机-89C51部分:5、点亮LED
飞书文档https://x509p6c8to.feishu.cn/wiki/SlB5wYD1QiPRzWkfijEcIvv8nyc 一、应用场景 二、点灯原理 插件led灯珠长引脚为正极,短引脚为负极。 LED(发光二极管)两端存在电压差,有一定的电流流过时会亮起。电流可以理解为水流,…...
Lua 第10部分 模式匹配
10.1 模式匹配的相关函数 字符串标准库提供了基于模式的 4 个函数。 我们已经初步了解过函数 find 和 gsub,其余两个函数分别是 match 和 gmatch (Global Match 的缩写)。 函数 string.find 用于在指定的目标字符串中搜索指定的模式。最简单的模式就是一…...
Maven 4.0.0 模式-pom.xml配置详解
Maven 4.0.0 模式-pom.xml配置详解 此 pom.xml 文件涵盖了 Maven 4.0.0 模式支持的所有主要标签,包括项目元数据、依赖管理、构建配置、发布管理等。每个标签都配有详细注释,说明其作用、常见用法和可能的值。 此文件旨在展示标签的完整性&#…...
IDEA 连接 Oracle 数据库
IDEA 连接 Oracle 数据库...
机器人快速启动
机器人快速启动 ES机器人开机操作流程 方法一(一体化底座启动) 接通48V电源点击底座“Power”按钮观察电源指示灯亮起,蜂鸣器发出“嘀”声,代表底座启动完成 方法二(控制手柄启动) 长按手柄开关机键2秒后松…...
使用 MediaPipe 和 OpenCV 快速生成人脸掩膜(Face Mask)
在实际项目中,尤其是涉及人脸识别、换脸、图像修复等任务时,我们经常需要生成人脸区域的掩膜(mask)。这篇文章分享一个简单易用的小工具,利用 MediaPipe 和 OpenCV,快速提取人脸轮廓并生成二值掩膜图像。 …...
《全球反空间能力》报告翻译——部分1
全球反空间能力 已进行过破坏性反卫星测试的国家 美国 美国目前拥有世界上最先进的军事太空能力,尽管与中国的相对差距正在缩小。在冷战期间,美国开创了许多现今使用的国家安全太空应用,并在几乎所有类别中保持技术领先地位。美国军方在将…...
云原生课程-Docker
一次镜像,到处运行。 1. Docker详解: 1.1 Docker简介: Docker是一个开源的容器化平台,可以帮助开发者将应用程序和其依赖的环境打包成一个可移植的,可部署的容器。 docker daemon:是一个运行在宿主机(DO…...
组件的基本知识
组件 组件的基本知识 组件概念组成步骤好处全局注册生命周期scoped原理 父子通信步骤子传父 概念 就是将要复用的标签,抽离放在一个独立的vue文件中,以供主vue文件使用 组成 三部分构成 template:HTML 结构 script: JS 逻辑 style: CSS 样…...
空间矩阵的思考
今天又看了些线性代数,引发了许多思考。 矩阵是以长和宽存储数据,那有没有一种新型的矩阵,以长宽高的形式存储数据呢?我不知道有没有,所以暂且称其为空间矩阵。 它肯定是存在的,可以这样抽象&#…...
【数据挖掘】时间序列预测-常用序列预测模型
常用序列预测模型 (1)AR(自回归)模型(2)ARIMA模型(3)Prophet模型(4)LSTM模型(5)Transformer模型(6)模型评估6.…...
将你的本地项目发布到 GitHub (新手指南)
目录 第 1 步:在 GitHub 上创建新的仓库 (Repository)第 2 步:将本地仓库连接到 GitHub 远程仓库第 3 步:(可能需要) 重命名你的默认分支第 4 步:将本地代码推送到 GitHub第 5 步:在 GitHub 上检查结果后续工作流程 你…...
[论文梳理] 足式机器人规划控制流程 - 接触碰撞的控制 - 模型误差 - 自动驾驶车的安全合规(4个课堂讨论问题)
目录 问题 1:足式机器人运动规划 & 控制的典型流程 (pipline) 1.1 问题 1.2 目标 1.3 典型流程(Pipeline) 1.3.1 环境感知(Perception) 1.3.2 高层规划(High-Level Planning) 1.3.3 …...
初中级前端面试全攻略:自我介绍模板、项目讲解套路与常见问答
为了给面试官留下专业而亲切的第一印象,自我介绍要突出与岗位相关的技能和项目经验,同时以自己擅长的领域开放式结尾。通常可以按照以下思路组织自我介绍内容:首先简单介绍个人信息和工作年限,然后列出精通的前端技术栈…...
Android开发中svg转xml工具使用
要使用 svg2vector-cli 工具通过命令行将 SVG 文件转换为 Android 可用的 XML 矢量图标文件,可以单个文件转换或者整个文件夹批量转换,以下是详细的步骤和说明: 1. 准备工作 1.1 下载工具 首先需要下载 svg2vector-cli-1.0.0.jar 或更高版本…...
爬虫技术入门:基本原理、数据抓取与动态页面处理
引言 在当今数据驱动的时代,网络爬虫技术已成为获取和分析互联网数据的重要手段。无论是搜索引擎的网页收录、竞品数据分析,还是学术研究的语料收集,爬虫技术都发挥着关键作用。本文将深入浅出地讲解爬虫的基本原理,分析它能获取…...
AI预测3D新模型百十个定位预测+胆码预测+去和尾2025年4月27日第65弹
从今天开始,咱们还是暂时基于旧的模型进行预测,好了,废话不多说,按照老办法,重点8-9码定位,配合三胆下1或下2,杀1-2个和尾,再杀6-8个和值,可以做到100-300注左右。 (1)定…...
服务器数据备份,服务器怎么备份数据呢?
企业数据量呈指数级增长,服务器数据备份已成为保障业务连续性、抵御勒索攻击与合规审查的核心技术环节。当前,服务器数据备份方案需兼顾数据完整性、恢复时效性、存储经济性三大核心诉求,其实现路径可根据技术架构、数据规模及容灾等级划分为…...
语音识别质量的跟踪
背景 这个项目是用来生成结构化的电子病历的。数据的来源是医生的录音。中间有一大堆的处理,语音识别,关键字匹配,结构化处理,病历编辑......。最多的时候给上百家医院服务。 语音识别质量的跟踪 一、0225医院的训练后的情况分…...
【数据挖掘】时间序列预测-时间序列的平稳性
时间序列的平稳性 (1)平稳性定义(2)平稳性处理方法2.1 差分法2.2 季节调整(Seasonal Adjustment)2.3 趋势移除(Detrending)2.4 对数转换(Logarithmic Transformation&…...
成都蒲江石象湖旅游攻略之石象湖郁金香最佳观赏时间
石象湖坐落于成都蒲江,拥有绝美的郁金香花海,吸引了很多的游客。如果大家想要观赏比较诱惑人的郁金香,那自然就应该知道正确的观赏时间。 心想郁金香合适的时间是每年的3月份到3月底。石象湖会还会举办盛大的郁金香节,在花园内有数…...
大模型、知识图谱和强化学习三者的结合,可以形成哪些研究方向?
大模型(Large Language Models, LLMs)、知识图谱(Knowledge Graph, KG)与强化学习(Reinforcement Learning, RL)作为人工智能领域的三大核心技术,其融合正推动着认知智能迈向新高度。本文结合2023-2025年的最新研究成果,系统梳理三者结合的七大科研方向及其技术路径。 …...
Linux文件操作
在C语言中,我们已经学习了文件相关的知识,那么在Linux中我们为什么还要再来学习文件呢?这是因为C语言中和Linux中,"文件"是2个不同的概念。所以我们要来学习Linux中对文件的操作。 在学习之前,我们先来回顾一…...
PostSwigger Web 安全学习:CSRF漏洞3
CSRF 漏洞学习网站:What is CSRF (Cross-site request forgery)? Tutorial & Examples | Web Security Academy CSRF Token 基本原理 CSRF Token 是服务端生成的唯一、随机且不可预测的字符串,用于验证客户端合法校验。 作用:防止攻击…...
【Node.js 】在Windows 下搭建适配 DPlayer 的轻量(简陋)级弹幕后端服务
一、引言 DPlayer官网:DPlayer 官方弹幕后端服务:DPlayer-node MoePlayer/DPlayer-node:使用 Docker for DPlayer Node.js 后端(https://github.com/DIYgod/DPlayer) 本来想直接使用官网提供的DPlayer-node直接搭建…...
淘宝tb.cn短链接生成
淘宝短链接简介 1. 一键在线生成淘宝短链接tb.cn,m.tb.cn等 2. 支持淘宝优惠券短链接等淘宝系的所有网址 3. 生成的淘宝短链接是官方的,安全稳定有保证 4.适合多种场景下使用,如:网站推广,短信推广 量大提供api接口࿰…...