大文件上传,对接阿里oss采用前端分片技术。完成对应需求!
最近做了一个大文件分片上传的功能,记录下
1. 首先是安装阿里云 oss 扩展
composer require aliyuncs/oss-sdk-php
去阿里云 oss 获取配置文件
AccessKey ID = ***
AccessKey Secret = ***
Bucket名称 = ***
Endpoint = ***
2. 前端上传,对文件进行分片
<form id="add-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action=""><div class="form-group"><label class="control-label col-xs-12 col-sm-2">{:__('选择本地文件')}:</label><div class="col-xs-12 col-sm-8"><input type="file" id="fileInput"><div><a href="#" onclick="startUpload()"><i class="fa fa-upload"></i>选择完点击上传(请等待上传完成)</a></div><div id="progress" style="margin-top:10px;"></div></div></div><div class="form-group layer-footer"><label class="control-label col-xs-12 col-sm-2"></label><div class="col-xs-12 col-sm-8"><button type="submit" class="btn btn-primary btn-embossed disabled">{:__('OK')}</button></div></div>
</form><script>let chunkSize = 5 * 1024 * 1024; // 分片大小5MBlet uploadId = '';let objectName = '';let parts = [];const CHUNK_SIZE = 5 * 1024 * 1024; // 分片阈值5MBasync function startUpload() {const file = document.getElementById('fileInput').files[0];if (!file) {layer.msg('请选择文件', {icon: 1});}// 根据文件大小选择上传方式if (file.size <= CHUNK_SIZE) {await directUpload(file);} else {await chunkedUpload(file);}}// 开始上传async function directUpload(file) {const formData = new FormData();formData.append('file', file);formData.append('file_name', file.name);// 显示进度条const progressBar = document.getElementById('progress');progressBar.innerHTML = '上传进度:0%';try {const res = await fetch('/api/directUpload', {method: 'POST',body: formData,});const data = await res.json();if (data.code === 1) {progressBar.innerHTML = '上传进度:100%';$("#c-name").val(data.name);$("#c-fullurl").val(data.fullurl);layer.msg('上传成功', {icon: 1});} else {throw new Error(data.msg);}} catch (error) {progressBar.innerHTML = '上传失败';console.error('直接上传失败:', error);}}async function chunkedUpload(file) {const totalChunks = Math.ceil(file.size / CHUNK_SIZE);let uploadedChunks = 0;// 初始化分片上传const initRes = await fetch('api/initUpload', {method: 'POST',body: JSON.stringify({filename: file.name}),headers: {'Content-Type': 'application/json'}});const initData = await initRes.json();if (initData.code !== 1) return alert('初始化失败');const {uploadId, objectName} = initData;const parts = [];// 上传所有分片for (let i = 0; i < totalChunks; i++) {const start = i * CHUNK_SIZE;const end = Math.min(start + CHUNK_SIZE, file.size);const chunk = file.slice(start, end);const formData = new FormData();formData.append('part', chunk);formData.append('uploadId', uploadId);formData.append('objectName', objectName);formData.append('partNumber', i + 1);const uploadRes = await fetch('api/uploadPart', {method: 'POST',body: formData});const partData = await uploadRes.json();if (partData.code === 1) {parts.push({PartNumber: partData.partNumber,ETag: partData.etag});uploadedChunks++;// 更新进度const progress = (uploadedChunks / totalChunks * 100).toFixed(2);document.getElementById('progress').innerHTML = `上传进度:${progress}%`;}}// 合并分片const completeRes = await fetch('api/completeUpload', {method: 'POST',body: JSON.stringify({uploadId,objectName,parts: JSON.stringify(parts)}),headers: {'Content-Type': 'application/json'}});const completeData = await completeRes.json();if (completeData.code === 1) {$("#c-name").val(completeData.name);$("#c-fullurl").val(completeData.fullurl);layer.msg('上传成功', {icon: 1});} else {layer.msg('上传失败' + completeData.msg, {icon: 2});}}
</script>
2. 后端控制器
<?phpnamespace app\****;use OSS\Core\OssException;
use OSS\OssClient;class Attachment
{// 初始化分片上传public function initUpload(){$object = 'uploads/' . date('Ymd') . '/' . $this->request->post('filename');try {$ossClient = new OssClient(config('alioss.accessKeyId'),config('alioss.accessKeySecret'),config('alioss.endpoint'));$uploadId = $ossClient->initiateMultipartUpload(config('alioss.bucket'), $object);return json(['code' => 1,'uploadId' => $uploadId,'objectName' => $object]);} catch (OssException $e) {return json(['code' => 0, 'msg' => $e->getMessage()]);}}// 上传分片public function uploadPart(){$data = $this->request->post();try {$ossClient = new OssClient(config('alioss.accessKeyId'),config('alioss.accessKeySecret'),config('alioss.endpoint'));$options = [OssClient::OSS_FILE_UPLOAD => $_FILES['part']['tmp_name'],OssClient::OSS_PART_NUM => $data['partNumber'],OssClient::OSS_CHECK_MD5 => true];$etag = $ossClient->uploadPart(config('alioss.bucket'),$data['objectName'],$data['uploadId'],$options);return json(['code' => 1,'etag' => $etag,'partNumber' => $data['partNumber']]);} catch (OssException $e) {return json(['code' => 0, 'msg' => $e->getMessage()]);}}// 完成上传public function completeUpload(){$data = $this->request->post();try {$ossClient = new OssClient(config('alioss.accessKeyId'),config('alioss.accessKeySecret'),config('alioss.endpoint'));$result = $ossClient->completeMultipartUpload(config('alioss.bucket'),$data['objectName'],$data['uploadId'],json_decode($data['parts'], true));return json(['code' => 1,'url' => $result['oss-request-url'],'name' => pathinfo($data['objectName'], PATHINFO_FILENAME),'fullurl' => strstr($result['oss-request-url'], '?', true),]);} catch (OssException $e) {return json(['code' => 0, 'msg' => $e->getMessage()]);}}// 直接上传完整文件public function directUpload(){try {$ossClient = new OssClient(config('alioss.accessKeyId'),config('alioss.accessKeySecret'),config('alioss.endpoint'));$file = $_FILES['file'];$file_name = $this->request->request('file_name', '');$object = 'uploads/' . date('Ymd') . '/' . $file['name'];$result = $ossClient->uploadFile(config('alioss.bucket'),$object,$file['tmp_name']);return json(['code' => 1,'url' => $result['oss-request-url'],'name' => pathinfo($file_name, PATHINFO_FILENAME),'fullurl' => $result['oss-request-url'],]);} catch (OssException $e) {return json(['code' => 0, 'msg' => $e->getMessage()]);}}
}