CodeIgniter框架下PHP+JQuery上传AWS S3时进度条仅跟踪本地问题
我太懂这个痛点了!之前帮朋友调过类似的CodeIgniter+S3上传场景,进度条唰一下跑到100%,结果用户盯着空等半天——因为前端只跟踪了「文件传到自己服务器」的那一小段,真正耗时的「服务器传去S3」的过程完全黑箱,体验糟透了。
下面给你两种针对性的解决方案,按需选:
方案一:PHP中转+实时推送S3上传进度(适合必须经过服务器处理的场景)
如果你的业务逻辑要求文件必须先过PHP(比如验证权限、修改文件内容、写入数据库记录),那可以利用AWS SDK的进度回调,结合PHP的输出缓冲,把S3上传的进度实时推给前端。
1. CodeIgniter控制器代码调整
先关闭CI的输出缓冲,避免进度被缓存,然后给S3客户端添加进度监控中间件:
public function upload_to_s3() { // 关闭输出缓冲,确保进度实时返回 $this->output->set_header('Content-Type: text/plain'); $this->output->set_header('Cache-Control: no-cache'); $this->output->set_header('Connection: keep-alive'); // 获取前端上传的文件 $uploadedFile = $_FILES['user_file']; $bucketName = '你的存储桶名称'; $s3Key = 'uploads/' . uniqid() . '_' . $uploadedFile['name']; // 初始化S3客户端 $s3 = new Aws\S3\S3Client([ 'version' => 'latest', 'region' => '你的存储桶区域', ]); // 创建上传命令 $uploadCommand = $s3->getCommand('PutObject', [ 'Bucket' => $bucketName, 'Key' => $s3Key, 'SourceFile' => $uploadedFile['tmp_name'], ]); // 添加进度监控中间件 $handler = $s3->getHandlerList()->resolve(); $handler = Aws\Middleware::monitor($handler, function ($transferStats) { if ($transferStats->hasResponse()) { // 上传完成 echo "progress:100\n"; flush(); } else { // 计算并输出当前进度 $progressData = $transferStats->getTransferProgress(); $percent = round(($progressData['bytes_uploaded'] / $progressData['total_bytes']) * 100); echo "progress:$percent\n"; flush(); // 强制输出缓冲区内容 } }); $s3->getHandlerList()->setHandler($handler); try { $s3->execute($uploadCommand); echo "done:success\n"; flush(); } catch (Exception $e) { echo "done:error\n"; flush(); } }
2. 前端代码调整
监听AJAX的onreadystatechange事件,实时解析PHP返回的进度数据:
const fileInput = document.getElementById('file-input'); const progressBar = document.getElementById('progress-bar'); fileInput.addEventListener('change', function(e) { const file = e.target.files[0]; const formData = new FormData(); formData.append('user_file', file); const xhr = new XMLHttpRequest(); xhr.open('POST', '/upload/upload_to_s3', true); xhr.onreadystatechange = function() { if (xhr.readyState === XMLHttpRequest.LOADING) { // 分割多次返回的进度数据 const responseParts = xhr.responseText.split('\n'); responseParts.forEach(part => { if (part.startsWith('progress:')) { const percent = parseInt(part.split(':')[1]); progressBar.style.width = `${percent}%`; progressBar.textContent = `${percent}%`; } else if (part.startsWith('done:')) { const status = part.split(':')[1]; status === 'success' ? alert('上传完成!') : alert('上传失败,请重试'); } }); } }; xhr.send(formData); });
关键配置注意
在CodeIgniter的config.php里关闭输出压缩,否则进度会被缓存:
$config['compress_output'] = FALSE;
同时调整PHP的max_execution_time,给大文件上传留足够时间。
方案二:预签名URL直接前端上传(更高效,推荐)
如果你的业务不需要服务器额外处理文件,直接让前端用预签名URL上传到S3是最优解——不仅减轻服务器压力,还能让前端的XHR直接监控到上传到S3的完整进度。
1. CodeIgniter生成预签名URL
public function get_presigned_url() { $fileName = $this->input->post('file_name'); $fileType = $this->input->post('file_type'); $s3 = new Aws\S3\S3Client([ 'version' => 'latest', 'region' => '你的存储桶区域', ]); $command = $s3->getCommand('PutObject', [ 'Bucket' => '你的存储桶名称', 'Key' => 'uploads/' . uniqid() . '_' . $fileName, 'ContentType' => $fileType, 'ACL' => 'public-read' // 根据需求设置文件权限 ]); // 生成15分钟内有效的预签名URL $presignedUrl = $s3->createPresignedRequest($command, '+15 minutes')->getUri(); echo json_encode(['url' => (string)$presignedUrl]); }
2. 前端直接上传
const fileInput = document.getElementById('file-input'); const progressBar = document.getElementById('progress-bar'); fileInput.addEventListener('change', function(e) { const file = e.target.files[0]; // 先请求预签名URL fetch('/upload/get_presigned_url', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: `file_name=${encodeURIComponent(file.name)}&file_type=${encodeURIComponent(file.type)}` }) .then(res => res.json()) .then(data => { // 用预签名URL直接上传到S3 const xhr = new XMLHttpRequest(); xhr.open('PUT', data.url, true); xhr.setRequestHeader('Content-Type', file.type); // 直接监控S3上传进度 xhr.upload.onprogress = function(e) { if (e.lengthComputable) { const percent = Math.round((e.loaded / e.total) * 100); progressBar.style.width = `${percent}%`; progressBar.textContent = `${percent}%`; } }; xhr.onload = function() { xhr.status === 200 ? alert('上传完成!') : alert('上传失败,请重试'); }; xhr.send(file); }); });
两种方案各有适用场景:方案一适合需要服务器介入的业务,方案二更轻量高效。如果遇到SDK版本兼容、CI配置的细节问题,随时提!
内容的提问来源于stack exchange,提问作者David
相关产品推荐
相关产品推荐

