最近在做一个图片上传功能,需求是要搞个进度条,让用户知道上传进度。听起来简单对?结果踩了一堆坑,今天就来分享下这些坑以及如何爬出来。
先说下背景,我做的是一个图片分享社区,用户可以上传图片,然后其他用户可以点赞、评论。上传图片的功能原本是用PHP的move_uploaded_file实现的,很简单,但问题是用户上传大图片时,页面就卡住了,用户体验贼差。于是乎,进度条的需求就来了。
前端搞个上传进度条
前端得有个上传进度条的展示。我用的是HTML5的<input type="file">标签,加上<progress>标签。代码大概长这样:
然后是用JavaScript监听文件上传事件,获取上传进度。这里用到了XMLHttpRequest的upload属性,它会触发progress事件。代码长这样:
var fileInput = document.getElementById('upload');
var progressBar = document.getElementById('progressBar');
fileInput.addEventListener('change', function() {
var file = this.files[0];
var xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', function(e) {
if (e.lengthComputable) {
var percent = (e.loaded / e.total) * 100;
progressBar.value = percent;
}
}, false);
xhr.open('POST', 'upload.php', true);
xhr.send(new FormData(document.forms[0]));
});

看起来挺完美是?结果一测试,发现进度条确实动了,但上传完成后,PHP那边啥都没收到。问题出在哪?原来是因为FormData没有正确绑定文件。
解决FormData文件绑定问题
在xhr.send(new FormData(document.forms[0]))这句里,我原先以为会自动把文件绑定到FormData里,结果发现不是那么回事。得手动把文件加到FormData里,于是改成这样:
var formData = new FormData();
formData.append('file', fileInput.files[0]);
xhr.send(formData);
再测试,文件终于能上传了,进度条也正常显示了。但问题还没完,用户上传的图片可能会很大,服务器默认的上传文件大小限制是2MB,得改。
修改PHP上传文件大小限制
PHP默认的上传文件大小限制是2MB,修改这个限制得去改php.ini文件。找到upload_max_filesize和post_max_size这两个参数,改成你要的大小,例如10MB:
upload_max_filesize = 10M
post_max_size = 10M
改完后重启Apache或Nginx服务。你以为这就完事了?结果测试时发现,上传大文件时,进度条会卡在某个百分比不动了,然后PHP那边抛了个错误:413 Request Entity Too Large。这是Nginx的默认限制,得改Nginx配置。
解决Nginx上传文件大小限制
在Nginx的配置文件中,找到client_max_body_size参数,改成你要的大小,例如10MB:
client_max_body_size 10M;
改完后重启Nginx。再测试,大文件也能上传了,进度条也正常显示。但你以为这就完事了?用户上传的图片可能不是标准的图片格式,可能是恶意文件,得加个文件类型检查。
文件类型检查
在PHP端,可以通过$_FILES数组获取上传的文件信息,然后检查文件的MIME类型。代码长这样:
if (isset($_FILES['file'])) {
$file = $_FILES['file'];
$allowedTypes = array('image/jpeg', 'image/png', 'image/gif');
if (!in_array($file['type'], $allowedTypes)) {
die('只允许上传JPG、PNG、GIF格式的图片');
}
move_uploaded_file($file['tmp_name'], 'uploads/' . $file['name']);
}
看起来没问题?结果测试时发现,用户上传的图片文件名可能会有中文,导致PHP那边保存文件时乱码。得处理下文件名。
处理文件名乱码问题
文件名乱码是因为PHP默认的字符编码是ISO-8859-1,而上传的文件名可能是UTF-8编码。得先把文件名转成UTF-8编码,再保存。代码长这样:
}
$filename = mb_convert_encoding($file['name'], 'UTF-8', 'auto');
move_uploaded_file($file['tmp_name'], 'uploads/' . $filename);
}
再测试,文件名不乱码了。但问题又来了,用户上传的图片可能会重名,导致覆盖之前的图片。得给文件名加个时间戳。
文件名加时间戳
在文件名前加个时间戳,避免重名。代码长这样:
}
$filename = time() . '_' . $filename;
}
再测试,文件名不再重名了。但用户上传的图片可能会很大,导致服务器存储空间不够用。得加个图片压缩功能。
图片压缩
在PHP端,可以用GD库或ImageMagick库对图片进行压缩。我这里用GD库,代码长这样:
}
// 图片压缩

$image = imagecreatefromjpeg('uploads/' . $filename);
imagejpeg($image, 'uploads/' . $filename, 75); // 压缩质量为75%
imagedestroy($image);
}
再测试,图片上传后自动压缩,节省了存储空间。但用户上传的图片可能会有恶意脚本,得加个图片安全性检查。
图片安全性检查
在PHP端,可以用getimagesize函数检查图片的真实类型,防止用户上传恶意文件。代码长这样:
}
// 图片安全性检查
$imageInfo = getimagesize('uploads/' . $filename);
if ($imageInfo === false) {
unlink('uploads/' . $filename);
die('上传的文件不是有效的图片');
}
}
再测试,恶意文件被成功拦截了。但问题又来了,用户上传的图片可能会有敏感内容,得加个图片内容审核。
图片内容审核
在PHP端,可以用第三方API(如百度AI、腾讯AI)对图片内容进行审核,防止用户上传敏感图片。这里用百度AI的图片审核API,代码长这样:
}
1502

被折叠的 条评论
为什么被折叠?



