做信创项目的朋友应该都有体会,几乎每个政务、国企OA系统上线时,都会遇到一个绕不过去的需求:要在信创系统里用PHP后端实现500M甚至更大文件的上传。一次提交几百兆的影像资料、报表数据包,如果直接改php.ini的upload_max_filesize,前端卡死、后端超时、中间件报错轮番来,最后运营同事抱着U盘跑机房拷文件的场景我都见过不止一次。
这篇文章就围绕“信创系统 + PHP + 500M大文件上传”这个组合,把我实际跑通的一套分片上传方案完整拆开讲:前端怎么切、后端怎么接、合并怎么保证不出错、信创环境的浏览器和Web服务器有哪些坑。适合正在做国产化适配、或者刚接到大文件上传需求不知道从哪里下手的PHP开发同学参考。
1. 先想明白:信创环境给500M上传带来了哪些不一样的问题
1.1 信创这个定语,到底改变了什么
做一个信创项目,第一反应通常是“替换”:数据库换成达梦,中间件换成东方通,服务器换成麒麟系统,CPU换成飞腾。但真正到了业务代码层面,你会发现,PHP还是那个PHP,LNMP还是那套东西。大文件上传这件事,本质上并不因为“信创”两个字就变成另一门技术,它真正改变的,是运行环境的约束条件。
我在一个政务影像归档项目里实测过,服务器是麒麟V10,CPU是飞腾FT-2000,Web端用的是国产浏览器内核。同样是PHP 7.4、Nginx 1.20,一套在x86的CentOS上跑得好好的上传逻辑,搬到信创环境后出现了三类问题:Nginx默认的client_max_body_size没改、PHP-FPM进程池并发能力受CPU架构影响、前端浏览器对XMLHttpRequest和File API的支持参差不齐。
所以说,信创环境下的500M上传,方案的难点不在于“实现一个上传接口”,而在于“在资源受限、兼容性不确定的环境里,把上传做得稳定”。这个思路贯穿整篇文章,也是我踩过一圈坑之后最大的体会。
1.2 PHP上传大文件的四道默认闸门
如果不做任何特殊设计,直接用最原始的form表单POST上传,PHP环境里至少有四个参数会把500M卡死:
upload_max_filesize:默认2M,超过就报“文件过大”。post_max_size:默认8M,POST上来的内容超过这个值直接拒绝。max_execution_time:默认30秒,上传慢一点就掐断脚本。memory_limit:默认128M,虽然file_uploads走的是临时文件,但某些框架层代码会把整个文件读进内存。
这四个参数不是独立生效的,它们像四道闸门,任何一个先到都会导致失败。改php.ini不是不行,但500M的单个文件,意味着用户要在一个HTTP请求里持续占用PHP-FPM进程几分钟甚至更久,这在并发场景下是灾难。一个PHP-FPM进程默认同时只能处理一个请求,500M上传占着进程,后面的请求只能排队,整个系统的响应速度会肉眼可见地垮掉。
1.3 为什么分片不是“一种选择”而是“唯一选择”
我接触过不少项目组,第一反应是“把php.ini改大不就行了”。短平快,开发测试都过了,一到生产就翻车。原因很简单:500M单请求上传对网络稳定性要求极高,一旦断网、超时、页面刷新,用户只能从头再来。政务内网环境还好,如果涉及跨地域访问,这个体验基本没法用。
分片上传的核心思路,是把500M拆成几十个小块,每个小块独立上传、独立校验,全部完成后服务端再合并。这样单个请求的数据量小,受PHP配置限制影响小,还可以利用浏览器的并发能力同时传多个分片,速度反而更快。而且天然支持断点续传——传了一半断了,重启后只传没传完的分片,不用从头再来。
所以这个方案不是“我们要不要分片”的问题,是必须分片。这也是后面所有设计的基础。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 500M分片上传的总体设计:前端切分、后端归并
2.1 整体架构:一个简单的流程闭环
整体流程一张图就能说清楚:用户选择文件,前端读取文件对象,按设定好的分片大小切片,生成一个全局唯一的任务标识;然后初始化上传任务,后端记录文件信息和分片总数;接着前端并发上传各个分片,后端逐个接收、落盘、登记进度;全部上传完成后前端调用合并接口,后端把分片按顺序拼接成完整文件,校验大小和类型,返回最终存储路径。
这个闭环里有两个很容易被忽略的细节。
第一个是文件标识的生成。不能用文件名,因为同名文件会被覆盖;也不能只用前端生成的随机数,因为刷新页面后不知道要续传哪个任务。务实做法是前端生成一个UUID,再结合文件大小、文件最后修改时间生成一个签名,后端保存这个签名与文件信息的映射。这样即使页面刷新,只要文件没变,签名还是一样的,就能继续续传。
第二个是分片的顺序保证。HTTP请求的到达顺序不等于发送顺序,特别是并发上传时,后发的分片可能先到,先发的可能后到。所以合并时不能依赖请求到达顺序,必须依赖分片索引号。听起来是常识,但我在实际项目里见过合出来的文件是坏的,查了半天就是因为合并没有按索引排序。
2.2 前后端接口协议设计
不管用不用框架,后端至少要暴露三个接口:
init:初始化上传任务,传入文件名、文件大小、分片大小、分片总数,返回taskId。upload:接收单个分片,参数包含taskId、chunkIndex、分片内容,返回该分片是否接收成功。merge:上传完成后告诉后端可以合并了,合并完成后返回最终文件路径和大小。
接口参数常见的坑是:把分片内容base64编码后塞进JSON。500M / 5MB = 100个分片,每个分片5MB,base64编码后变约6.7MB,JSON传输效率极低,而且PHP解析这么大的JSON会消耗大量内存。正确做法是upload接口直接用multipart/form-data,分片作为文件字段上传,taskId和chunkIndex作为普通字段。这样后端直接拿到$_FILES,处理成本最低。
2.3 分片大小的选择:2MB还是10MB?
分片大小没有标准答案,要结合网络和应用场景。我实测过的经验值可以参考:
| 分片大小 | 500M分片数 | 适用场景 | 实测感受 |
|---|---|---|---|
| 2MB | 250片 | 网络不稳定、需要频繁续传 | 重传成本低,但请求往返多,整体偏慢 |
| 5MB | 100片 | 内网/外网均衡,普通项目推荐 | 请求数和重传成本平衡,合并也快 |
| 10MB | 50片 | 内网带宽充足、链路稳定 | 分片少,合并快,但单片失败重传成本高 |
除了网络因素,还有一个容易被忽略的点:服务器接收分片时要写临时文件,最终合并时要把所有分片拼起来。如果分片数量太多(比如1MB一片、500M就是500片),合并时的文件句柄开销和排序压力会变大。我一般推荐5MB,500M恰好100片,管理起来比较舒服,前端的进度条也好看。
3. 后端PHP代码核心实现:init、upload、merge三步走
3.1 初始化接口:记录上传任务
先看init接口。这个接口做什么?接收文件名、文件大小、分片大小、分片总数,生成taskId,创建任务目录,把任务信息写成一个JSON文件存起来。不用数据库也能跑,用文件系统做任务记录,对信创环境更友好,少一个依赖。
php复制<?php
// upload_init.php
// 简单示意,生产环境请加上权限校验
$file_name = $_POST['file_name'] ?? '';
$file_size = (int)($_POST['file_size'] ?? 0);
$chunk_size = (int)($_POST['chunk_size'] ?? 5 * 1024 * 1024);
$chunk_total = (int)($_POST['chunk_total'] ?? 0);
// 基础参数校验
if ($file_size <= 0 || $chunk_total <= 0 || empty($file_name)) {
exit(json_encode(['code' => 1, 'msg' => '参数错误']));
}
// 生成任务ID,用随机串而不是文件名
$taskId = md5(uniqid(mt_rand(), true));
// 创建任务目录
$taskDir = UPLOAD_TMP_DIR . '/' . $taskId;
if (!mkdir($taskDir, 0755, true)) {
exit(json_encode(['code' => 2, 'msg' => '创建任务目录失败']));
}
// 写入任务信息
$taskInfo = [
'file_name' => $file_name,
'file_size' => $file_size,
'chunk_size' => $chunk_size,
'chunk_total' => $chunk_total,
'created_at' => time(),
'uploaded' => [],
];
file_put_contents($taskDir . '/task.json', json_encode($taskInfo));
echo json_encode([
'code' => 0,
'task_id' => $taskId,
'msg' => 'ok'
]);
这里要注意两点:
第一,$file_name不能直接参与路径拼接,最终落地的文件名要由服务端重新生成,或者做严格的字符白名单校验,否则会引入路径穿越风险。信创环境尤其要注意,很多系统默认权限策略更严格,路径稍微不对就会报权限错误。
第二,任务目录权限。Nginx/Apache的工作用户和PHP-FPM的用户要保持一致,否则会出现“分片写成功了但合并时读不了”这种诡异问题。我在项目里遇到过两次,最后都是权限串了。
3.2 分片接收接口:校验、落盘、进度登记
先看一个直观的实现,再逐条说明这个实现里的坑。
php复制<?php
// upload_chunk.php
$taskId = $_POST['task_id'] ?? '';
$chunkIndex = (int)($_POST['chunk_index'] ?? -1);
$taskDir = UPLOAD_TMP_DIR . '/' . $taskId;
$taskFile = $taskDir . '/task.json';
if (!is_file($taskFile)) {
exit(json_encode(['code' => 3, 'msg' => '任务不存在']));
}
$taskInfo = json_decode(file_get_contents($taskFile), true);
if (!$taskInfo || $chunkIndex < 0 || $chunkIndex >= $taskInfo['chunk_total']) {
exit(json_encode(['code' => 3, 'msg' => '参数错误']));
}
// 分片落盘,文件名用固定位数补零
$chunkFile = $_FILES['chunk_file'] ?? null;
if (!$chunkFile || $chunkFile['error'] !== UPLOAD_ERR_OK) {
exit(json_encode(['code' => 4, 'msg' => '分片上传失败']));
}
$chunkPath = $taskDir . '/chunk_' . sprintf('%04d', $chunkIndex);
move_uploaded_file($chunkFile['tmp_name'], $chunkPath);
// 写入完成标记文件
file_put_contents($taskDir . '/chunk_' . sprintf('%04d', $chunkIndex) . '.ok', '1');
echo json_encode(['code' => 0, 'msg' => 'ok']);
这段代码有几个问题,我在实际项目中都踩过:
-
不要用
file_get_contents直接读分片。上面的代码用的是move_uploaded_file,这是正确的。早期版本我图省事,先把临时文件读进内存再file_put_contents,5MB分片内存峰值也就10MB左右,但并发一高就扛不住了。move_uploaded_file是文件系统层面的移动,PHP只是搬运工,内存占用几乎为零。 -
并发写
task.json会互相覆盖。早期我每收一个分片就更新一次task.json里的uploaded数组,结果并发上传时两个请求同时读写同一个JSON,后写的把先写的覆盖了,进度记录丢失,断点续传直接失效。后来改成写独立的.ok标记文件,一个分片一个标记,合并时扫描标记文件判断进度,从根本上避开了并发写同一个文件的竞争问题。 -
分片文件命名必须固定位数补零。
chunk_0001、chunk_0002、chunk_0010,按字典序排就是正确的顺序。如果直接叫chunk_1、chunk_2、chunk_10,字典序排在前面的是chunk_1、chunk_10、chunk_2,合并出来的文件就是坏的。这个坑我帮同事排查过,最后定位到就这一行代码的问题。
3.3 合并接口:顺序校验、流式拼接、完整性检查
merge接口是整个方案的临门一脚。先扫描.ok标记文件的数量是否等于chunk_total,不等于说明还有分片没传完,返回提示;等于则按文件名顺序逐块拼接。
php复制<?php
// upload_merge.php
$taskId = $_POST['task_id'] ?? '';
$taskDir = UPLOAD_TMP_DIR . '/' . $taskId;
if (!is_dir($taskDir) || !is_file($taskDir . '/task.json')) {
exit(json_encode(['code' => 3, 'msg' => '任务不存在']));
}
$taskInfo = json_decode(file_get_contents($taskDir . '/task.json'), true);
// 检查分片完整性
$okFiles = glob($taskDir . '/chunk_*.ok');
if (count($okFiles) < $taskInfo['chunk_total']) {
exit(json_encode([
'code' => 5,
'msg' => '分片未传完',
'done' => count($okFiles),
'total'=> $taskInfo['chunk_total']
]));
}
// 合并前加锁,防止重复合并
$lockFp = fopen($taskDir . '/merge.lock', 'w');
if (!flock($lockFp, LOCK_EX | LOCK_NB)) {
exit(json_encode(['code' => 6, 'msg' => '正在合并中']));
}
// 生成最终文件名,避免直接使用原始文件名
$ext = pathinfo($taskInfo['file_name'], PATHINFO_EXTENSION);
$finalPath = FINAL_UPLOAD_DIR . '/' . date('YmdHis') . '_' . uniqid() . '.' . $ext;
$fp = fopen($finalPath, 'wb');
for ($i = 0; $i < $taskInfo['chunk_total']; $i++) {
$chunkPath = $taskDir . '/chunk_' . sprintf('%04d', $i);
if (!is_file($chunkPath)) {
exit(json_encode(['code' => 7, 'msg' => '分片缺失: ' . $i]));
}
$chunkFp = fopen($chunkPath, 'rb');
stream_copy_to_stream($chunkFp, $fp, 1024 * 1024);
fclose($chunkFp);
}
fclose($fp);
// 校验文件大小
if (filesize($finalPath) !== $taskInfo['file_size']) {
unlink($finalPath);
exit(json_encode(['code' => 8, 'msg' => '文件大小校验失败']));
}
// 清理临时目录
array_map('unlink', glob($taskDir . '/chunk_*'));
array_map('unlink', glob($taskDir . '/*.ok'));
unlink($taskDir . '/task.json');
rmdir($taskDir);
echo json_encode(['code' => 0, 'msg' => 'ok', 'file_path' => $finalPath]);
合并阶段有两个性能点必须注意。
第一个是合并方式。早期版本我用file_get_contents把每个分片读进内存再fwrite,100个5MB分片,理论峰值500MB,PHP的memory_limit是128M,直接崩。后来改成stream_copy_to_stream按1MB一块流式拷贝,内存占用恒定为1MB左右,500M合并完也很稳。记住:大文件处理,一切全量读进内存的做法都是自找麻烦。
第二个是合并锁。前端有可能同时发多个merge请求,用户也可能手抖多点一次按钮。如果两次merge同时跑,两个进程同时写同一个最终文件,文件必坏。用flock加独占锁,第二个请求进来直接返回“正在合并中”,前端轮询一下再拿结果就是了。
4. 信创环境下的兼容性实测:浏览器、Web服务器、并发合并
4.1 国产浏览器与前端分片:Worker能不能用
前端分片用File API,读取用Blob.slice,上传用XMLHttpRequest。这些API在国产浏览器上的支持情况参差不齐,我实测过几款:
- 360安全浏览器极速模式:基于Chromium,
Blob.slice、FileReader、XMLHttpRequest都支持,表现最好。 - 奇安信可信浏览器:Chromium内核,支持度好,但版本较新,ES6语法没问题。
- 红莲花安全浏览器:基于Firefox ESR,支持File API,但部分老版本对fetch的兼容需要注意,建议统一用XMLHttpRequest作为上传发送的封装。
- UOS自带浏览器:不同版本差异大,老版本可能不支持可选链、箭头函数等ES6+语法,前端代码要编译到ES5。
所以前端这块我的建议是:核心上传逻辑用XMLHttpRequest,不用fetch;代码用Babel编译到ES5;文件切片用Blob.slice(注意老版本浏览器可能需要加前缀或参数不同);不要一开始就指望Web Worker。
那Worker用在哪?我后来在真正需要Web Worker的场景是“计算文件MD5”。500M文件算MD5要好几秒,在主线程做会卡住页面,用户点完上传按钮页面像死了一样。但实话实说,信创环境的终端CPU性能普遍一般,就算用了Worker,计算500M的MD5也需要十几秒,体验很尴尬。所以我的方案里对MD5做了妥协:只算每个分片的MD5,而不是整个文件的MD5。每个分片在上传前计算MD5,传到后端,后端落盘时也计算MD5并比对,这样既验证了网络传输的完整性,又不会在用户端卡太久。整体文件完整性靠文件大小比对加抽样分片校验来兜底,够用。
4.2 Nginx与PHP-FPM的参数联调
就算分片了,Nginx和PHP-FPM的默认配置也得配合调整。分片上传虽然每个请求数据量小,但下面这几个参数是绕不开的:
client_max_body_size:Nginx默认1MB,必须调大。分片5MB的话,建议设为10m,留出冗余。client_body_timeout:默认60s,如果网络慢,单片上传可能超过60s,要调大到120s或300s。- PHP的
upload_max_filesize:保持2M完全够,但post_max_size要比单片大小略大,建议设为10M。 max_execution_time:接收分片的脚本执行时间很短,但合并接口耗时长,建议给合并接口单独设置set_time_limit(0),或者在PHP-FPM pool配置里单独调大。
还有一个Nginx细节:如果前端并发上传多个分片,每个请求都会占用一个Nginx worker连接和一个PHP-FPM进程。并发数上来后,要注意worker_connections和PHP-FPM的pm.max_children是否匹配。信创服务器内存通常比x86服务器紧张,pm.max_children按每个进程内存占用估算,不要把值设太大。我那个项目服务器只有8G内存,每个PHP-FPM进程稳定占80M左右,pm.max_children设了50就差不多了,再大内存直接爆。
4.3 合片时的并发与文件锁隐患
前面merge接口里已经写了一个flock锁,但还有一个隐患值得单独说:如果前端在页面关闭后还有未完成的请求,服务端进程还在跑,用户重新打开页面再次上传同一个文件,就会有两个上传任务同时写同一个目标文件。这种情况不常见,但一旦发生,文件是坏的,而且很难排查。
我的做法是:在init接口里检查同一签名是否已有未完成的任务,有就直接返回已有的taskId,不重新创建。如果已有任务已合并完成,直接返回最终文件路径,前端都不用重新传。这就是“秒传”的雏形,后面会展开讲。
另一个隐患是磁盘空间。500M文件合并时需要同时存在分片(500M)和最终文件(500M),如果上传分区只剩600M,合并就会失败。我在项目里踩过,后来在init接口里加了磁盘剩余空间检查,小于文件大小的1.2倍就直接拒绝上传,给用户明确提示。
5. 安全防护与性能优化:不只是“能传上去”
5.1 文件类型校验与二次检测
上传接口最怕的是有人直接传一个PHP文件上来,然后通过路径访问把它执行了。分片上传更容易绕过一些防护,因为分片本身不完整,不能直接读文件头判断类型。所以至少要做三道校验:
第一道,初始化接口按扩展名做白名单校验,允许doc、docx、xls、xlsx、pdf、zip、rar、jpg、png、mp4等。不要用黑名单,黑名单永远防不住。
第二道,合并完成后用finfo_file读取实际MIME类型,和扩展名比对,不一致直接删除。比如一个文件叫test.docx,实际MIME是application/x-php,这种直接干掉。
php复制$finfo = new finfo(FILEINFO_MIME_TYPE);
$mimeType = $finfo->file($finalPath);
$allowedMime = ['application/pdf', 'image/jpeg', 'application/zip', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'];
if (!in_array($mimeType, $allowedMime)) {
unlink($finalPath);
exit(json_encode(['code' => 9, 'msg' => '文件类型校验失败']));
}
第三道,最终文件存储目录如果和Web根目录重叠,一定要配置禁止脚本执行。比如上传目录下所有PHP文件直接返回403,或者干脆把上传目录放在Web根目录之外,用单独的下载接口输出文件。这是安全底线,不能省。
注意一个信创环境的坑:某些信创环境下的office软件会导出扩展名为docx但MIME类型是application/octet-stream的文件,如果MIME白名单太严格会误伤。我的做法是:扩展名白名单是最外层防线,MIME校验只对可执行文件(php、exe、sh等)强制拦截,office文件只做解压内容的入口检查。这个平衡需要根据业务场景调。
5.2 并发控制与任务清理
上传任务是有状态的,如果用户传了一半不传了,临时分片会一直留在服务器上。必须有一个清理机制:
- 每次init时,顺手清理同IP下超过24小时未完成的任务。
- 用crontab每天凌晨扫描上传临时目录,删除最后修改时间超过48小时的taskDir。
- 合并成功后立即删除分片目录,这个在merge接口里已经做了。
并发控制方面,除了IP限制,还可以按taskId维度做令牌。每个分片上传前,后端判断已上传分片数与当前分片索引的差距,如果差距过大(比如前端同时只允许发5个请求,但已上传了10个,说明有异常),返回错误。这个逻辑可以省不少事,但不是必须的,项目初期可以不加,等真有并发问题再说。
5.3 磁盘IO与临时文件目录规划
大文件上传本质上是磁盘IO密集型操作。几个优化点:
-
上传临时目录和最终存储目录分开,且不要和系统分区放一起。信创环境下很多服务器系统盘不大,应用数据盘单独挂载,这一步尤其重要。我见过把上传临时目录放在根分区的,传了几个G之后系统盘满了,连登录都登不上去。
-
如果服务器是机械盘,合并时顺序读分片再顺序写最终文件,IO性能问题不大。如果是多块盘,也可以把分片临时目录分散到不同磁盘以提升并发性能,但一般500M场景没必要,徒增复杂度。
-
PHP-FPM进程写文件时要注意文件权限。Nginx/Apache的工作用户和PHP-FPM的用户要保持一致,否则会出现“分片写成功了但合并时读不了”的权限问题。这个问题在信创环境很常见,因为默认权限策略可能更严格。
-
如果最终存放目录和Web根目录重叠,一定要配置禁止脚本执行,前面已经强调过,这里再重复一次,因为太重要了。
6. 踩坑记录与后续扩展建议
6.1 连踩三坑:超时、磁盘爆满、合并乱序
第一个坑是前端并发上传8个分片,后端突然大面积超时。查了半天,发现是PHP-FPM的pm.max_children设成了4,8个并发请求一进来直接排队,前一批还没处理完,后一批就超时了。解决:把前端上传并发数调整为3,或者根据服务器CPU核数和PHP-FPM进程数动态设定。信创环境CPU核数普遍不多,我最后设成3并发上传,进度虽然慢了但稳定,比大面积超时强太多。
第二个坑是合并到一半提示磁盘空间不足。原因是我没有在初始化时做空间预检,一个500M文件上传过程中,系统日志、其他业务又把磁盘占满了。后来在init接口里加了一个磁盘空间检查,小于文件大小的1.2倍直接拒绝,并且合并前再检查一次,双保险。这个问题在信创服务器上尤其突出,很多机器数据盘和系统盘是分开的,但临时目录默认放在系统盘,容易踩雷。
第三个坑是合并出来的文件损坏,播放器、解压工具提示文件不完整。排查下来是合并没有校验分片数量和顺序。有个分片网络超时了,前端自动重传时chunkIndex传错了,多传了一次相同索引的分片,导致分片总量达标但缺了一个分片。后来我加了个规则:合并前遍历所有chunkIndex,检查是否0到chunk_total-1全部有且只有一份,缺一不可。这个检查成本很低,但能拦截大多数合并乱序问题。
6.2 后续扩展:断点续传到秒传、OSS迁移
这套分片上传方案跑稳之后,扩展方向很清晰。
断点续传:前端在init后保存taskId到localStorage,下次进入页面时,如果本地有未完成任务,先调用进度查询接口,拿到已上传分片列表(扫描.ok文件就能得到),跳过它们只传剩余分片。这就是断点续传。核心代码就一个进度查询接口,成本很低,但用户感知提升非常明显。
秒传:在初始化接口里,如果发现相同大小、相同文件唯一标识已存在于存储目录,直接返回“已完成”,前端连上传都不用。前提是要在合并时计算整个文件的MD5或SHA1并入库。500M文件算MD5要十几秒,但秒传带来的体验提升值得做。具体做法是在merge结束后计算一次MD5,存到一个file_hash表,init时查一次。
OSS对象存储迁移:把“分片接收、落盘、合并”改成“分片接收、直传OSS、合并时调用OSS的CompleteMultipartUpload”,接口设计基本不用变,只改合并逻辑。我的实际经验是,前期接口协议设计得独立一点,后面迁移到OSS或者私有对象存储会非常顺畅,不用动前端一行代码。
最后再说一个自己的体会。这个方案从第一版到稳定运行,前后调了两周,大部分时间不是花在功能开发上,而是花在和信创环境的配置、兼容性、权限这些“看不见的地方”死磕。所以如果你们刚开始做信创项目,我给一条最实在的建议:先别急着写代码,先把运行环境的所有参数摸清楚,特别是PHP-FPM的进程配置、Nginx的body大小限制、目录权限、磁盘空间。这几个基础项搞不定,后面所有代码都是白搭。
