首页 > 其他分享 >大文件上传如何做断点续传?全端+后端结合开发

大文件上传如何做断点续传?全端+后端结合开发

时间:2023-10-14 14:55:36浏览次数:31  
标签:断点续传 const upload value 全端 file 分片 上传

断点续传是什么?
断点续传(Resumable File Upload)是一种文件上传的技术,它允许在上传过程中出现中断或失败的情况下,能够从中断的位置继续上传,而不需要重新上传整个文件。这在处理大文件或不稳定的网络连接时非常有用。

断点续传的实现通常涉及以下几个关键概念和步骤:

分片:将大文件分割成较小的文件块(通常是固定大小的块),每个块都有一个唯一的标识符。

上传请求:客户端发起上传请求,并将文件分片按顺序上传到服务器。

上传状态记录:服务器端需要记录上传的状态,包括已接收的分片、分片的顺序和完整文件的大小等信息。

中断处理:如果上传过程中发生中断(例如网络中断、用户主动中止等),客户端可以记录已上传的分片信息,以便在恢复上传时使用。

恢复上传:当上传中断后再次开始上传时,客户端可以发送恢复上传请求,并将已上传的分片信息发送给服务器。

服务器处理:服务器接收到恢复上传请求后,根据已上传的分片信息,判断哪些分片已经上传,然后继续接收剩余的分片。

合并文件:当所有分片都上传完成后,服务器将所有分片按顺序组合成完整的文件。

下面是一个简化的断点续传流程图:

客户端 服务器
| |
|------ 发起上传请求 ------------------>|
| |
|------ 上传分片1 -------------------->|
| |
|------ 上传分片2 -------------------->|
| |
| ... |
| |
|------ 上传分片N -------------------->|
| |
|------ 中断或失败 -------------------->|
| |
|------ 发起恢复上传请求 --------------->|
| |
|------ 发送已上传分片信息 ------------>|
| |
| ... |
| |
|------ 上传剩余分片 ------------------>|
| |
|------ 上传完成 ---------------------->|
| |
|------ 合并分片为完整文件 ------------>|
| |
|<----- 上传成功响应 --------------------|
| |

上述流程图描述了客户端和服务器之间的交互过程。客户端发起上传请求,并逐个上传分片,如果中断或失败,客户端可以恢复上传并将已上传的分片信息发送给服务器。服务器根据已上传的分片信息,继续接收剩余的分片。当所有分片上传完成后,服务器将它们合并成完整的文件,并向客户端发送上传成功的响应。

断点续传技术可以提高文件上传的可靠性和效率,特别是在处理大文件或不稳定的网络环境时。它可以减少重新上传的数据量,节省带宽和时间,并提供更好的用户体验

断点续传实现
1.前端对文件进行分块

2.前端使用多线程上传分片,上传前给服务器发送消息验证当前分片是否已经上传。

3.所有分片上传完毕后,发送合并分片请求,校验文件的完整性。 (上传的分片应该具备顺序标记)

4.前端给服务器传一个MD5值,服务器合并文件后,利用MD5值计算是否与源文件一致。如果不一致,说明文件需要重新上传。

分片文件清理问题:

在数据库中有一张文件表记录minIo中存储的文件信息
文件开始上传时会写入文件表,状态为上传中,上传完成会更新状态为上传完成
当一个文件传了一半不再上传了,说明该文件没有上传完成,通过定时任务去查询文件表中的记录,如果文件距离上次上传结束超过24小时,则可以考虑清除MinIo中相关的分片数据

原理能是一大堆,代码如何去实现呢?
在这我使用的是前段vue3 + 后端的是基于tp5开发的fastadmin框架

前端部分:

<template>
<div>
<input type="file" @change="selectFile" />
<button @click="upload">上传</button>
<progress :value="progress" :max="100"></progress>
</div>
</template>
script部分:

<template>
<div>
<input type="file" @change="selectFile" />
<!-- <button @click="upload" :disabled="disabled">上传</button> -->
<progress :value="progress" :max="100"></progress>
<button @click="toggleUpload">{{ isUploading ? '停止上传' : '开始上传' }}</button>
</div>
</template>

<script setup>
import axios from 'axios';
import qs from 'qs';
import { ref, watch ,onMounted } from 'vue';
import { MD5, enc } from 'crypto-js';

const disabled = ref(false);
const serializedData = qs.stringify();
const tableData = ref([]);

axios.post('/api/uploadss/index', serializedData)
.then(response => {
console.log(response.data);
tableData.value = response.data;
})
.catch(error => {
console.error(error);
});

const encodeMD5 = (md5Hash) => {
const part1 = md5Hash.substr(0, 6);
const part2 = md5Hash.substr(6, 6);
const part3 = md5Hash.substr(12, 6);
const part4 = md5Hash.substr(18, 6);
const part5 = md5Hash.substr(24, 8);

return `${part1}-${part2}-${part3}-${part4}-${part5}`;
};

const file = ref(null);
const progress = ref(0);
const isUploading = ref(false);

let filename;

const selectFile = (event) => {
file.value = event.target.files[0];
filename = `.${file.value.name.split('.')[1]}`;
};


let chunkSize;
let totalChunks;
let currentChunk;
let chunkIdValue;

const uploadChunk = (start, end) => {
const formData = new FormData();
const reader = new FileReader();
reader.onload = () => {
const imageData = reader.result;
if (currentChunk == 0) {
chunkIdValue = MD5(imageData);
}
formData.append('file', new File([file.value.slice(start, end)], filename));
formData.append('action', currentChunk === totalChunks ? 'merge' : 'clean');
formData.append('chunkindex', currentChunk);
formData.append('chunkid', encodeMD5(chunkIdValue.toString(enc.Hex)));
formData.append('chunkcount', totalChunks);
formData.append('filename', filename);

axios.post('/api/uploadss/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
onUploadProgress: (progressEvent) => {
const progressValue = Math.round((progressEvent.loaded / progressEvent.total) * 100);
progress.value = progressValue;
},
})
.then((res) => {
currentChunk++;
if (currentChunk <= totalChunks && isUploading.value) {
const start = currentChunk * chunkSize;
const end = Math.min(start + chunkSize, file.value.size);
uploadChunk(start, end);
} else {
console.log(res);
console.log('上传完成');
}
})
.catch((error) => {
console.error('上传失败:', error);
});
};
reader.readAsDataURL(file.value.slice(start, end));
};

const upload = () => {
if (!file.value) {
return;
}

console.log(1111);

chunkSize = 2 * 1024 * 1024; // 设置分片大小为8MB
totalChunks = Math.ceil(file.value.size / chunkSize);
currentChunk = 0;
chunkIdValue;

const start = 0;
const end = Math.min(chunkSize, file.value.size);
uploadChunk(start, end);
return uploadChunk;
};

const toggleUpload = () => {
isUploading.value = !isUploading.value;

if (isUploading.value) {
// 继续上传时,从存储中读取记录并恢复上传状态
const uploadRecord = localStorage.getItem('uploadRecord');
if (uploadRecord) {
const { currentChunk, progressValue } = JSON.parse(uploadRecord);
progress.value = progressValue;
uploadChunk(currentChunk * chunkSize, file.value.size);
}
} else {
// 停止上传时,保存当前的分片索引和进度值到本地存储中
const uploadRecord = JSON.stringify({
currentChunk: currentChunk - 1,
progressValue: progress.value,
});
localStorage.setItem('uploadRecord', uploadRecord);
}
};

watch(isUploading, (value) => {
if (!value) {
// 中断上传并保存记录
console.log('停止上传');
const uploadRecord = JSON.stringify({
currentChunk: currentChunk - 1,
progressValue: progress.value,
});
localStorage.setItem('uploadRecord', uploadRecord);
} else {
// 恢复上传
console.log('继续上传');
upload();
}
});


onMounted(() => {
const handleOnline = () => {
console.log('网络已恢复,继续上传');
// if (file.value && progress.value !== 100) {
isUploading.value = true;
upload();
// }
};

const handleOffline = () => {
isUploading.value = false;
console.log('网络中断,停止上传');
};

window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);

return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
});


</script>

这些是vue3的script部分其中要注意的是参数的定义,在这里我使用的实例formeData进行传值向后端的接口发起请求,我们这里有调用了一个reader.onload的一个函数用来进行这个异步加载来访问接口.其中他的action的值他其他的值不同与其他的值不同(本来是可用不同的值,fastadmin框架需要这种类型的值)因为上面有一个前段的Md5的36为字符的加密想要使用需要在你的终端上输入

npm install crypto-js //下载md5算法的依赖

这里我们前段的值就传递了大不多了,其中有formData.append('filename', filename);这个值一开始是需要后端返回过来的,但实际上并不是,它是由前端传递他的后缀名;这些值传递完成后就是后端的操作流程到这里前端部分就完成了

后端部分(前面说过了使用的是fastadmin框架完成的后端,也就是需要去改框架代码);

这里我就编写一些需要改的连接以及调用的接口;

public function upload()
{
Config::set('default_return_type', 'json');
//必须设定cdnurl为空,否则cdnurl函数计算错误
Config::set('upload.cdnurl', '');
$chunkid = $this->request->post("chunkid");
if ($chunkid) {
if (!Config::get('upload.chunking')) {
$this->error(__('Chunk file disabled'));
}
$action = $this->request->post("action");
$chunkindex = $this->request->post("chunkindex/d");
$chunkcount = $this->request->post("chunkcount/d");
$filename = $this->request->post("filename");
$method = $this->request->method(true);

if ($action == 'merge') {
$attachment = null;
//合并分片文件
try {
$upload = new Upload();
$attachment = $upload->merge($chunkid, $chunkcount, $filename);
} catch (UploadException $e) {
// return 111;
$this->error($e->getMessage());
}
$this->success(__('Uploaded successful'), ['url' => $attachment->url, 'fullurl' => cdnurl($attachment->url, true)]);
} elseif ($method == 'clean') {
//删除冗余的分片文件
try {
$upload = new Upload();
$upload->clean($chunkid);
} catch (UploadException $e) {
$this->error($e->getMessage());
}
$this->success();
} else {
//上传分片文件
//默认普通上传文件
$file = $this->request->file('file');
try {
$upload = new Upload($file);
return $upload->chunk($chunkid, $chunkindex, $chunkcount);
} catch (UploadException $e) {
$this->error($e->getMessage());
}


}
}
else {
$attachment = null;
//默认普通上传文件
$file = $this->request->file('file');
try {
$upload = new Upload($file);
$attachment = $upload->upload();
} catch (UploadException $e) {
$this->error($e->getMessage());
}

$this->success(__('Uploaded successful'), ['url' => $attachment->url, 'fullurl' => cdnurl($attachment->url, true)]);
}
}

这里后端的接口(不要复制了,这里的代码在application/admin/controller/Ajax.php文件),因为使用的它自带的admin的上传(修改的幅度较大)根据情况也可以使用api的上传;首先我们要去打开多图片上传(application/extra/upload.php) 修改 'chunking' => false,改为true;根据上面所需要的值进行传值;这些我们在前段已经定义过了主要去说一下filename的值,这里有个判断是前段定义的,当action的值为不为merge访问chunk方法(地址:application/common/library)这里面都是upload等方法,包括处理分片合并,以及向public/uploads移入都可以.

以上就是简单点断点续传的示例

 

参考文章:http://blog.ncmem.com/wordpress/2023/10/14/%e5%a4%a7%e6%96%87%e4%bb%b6%e4%b8%8a%e4%bc%a0%e5%a6%82%e4%bd%95%e5%81%9a%e6%96%ad%e7%82%b9%e7%bb%ad%e4%bc%a0%e5%85%a8%e7%ab%af%e5%90%8e%e7%ab%af%e7%bb%93%e5%90%88%e5%bc%80%e5%8f%91/

欢迎入群一起讨论

 

 

标签:断点续传,const,upload,value,全端,file,分片,上传
From: https://www.cnblogs.com/songsu/p/17764161.html

相关文章

  • HTML5 大文件断点续传完整思路整理
    用html5的新特性分割文件,为达到断点续传功能用spark.js获取文件md5以确保文件的唯一性流程概述: 复制代码(此功能前端共需调用3个接口,分别为简称作A/B/C)1,获取文件信息:使用HTML5的原生上传input,选择文件后,获取文件的所有信息(文件名、文件总字节数等)......
  • HTML5实现文件断点续传的方法
    HTML5的FILEapi,有一个slice方法,可以将BLOB对象进行分割。前端通过FileList对象获取到相应的文件,按照指定的分割方式将大文件分段,然后一段一段地传给后端,后端再按顺序一段段将文件进行拼接。断点续传原理目前比较常用的断点续传的方法有两种,一种是通过websocket接口进行文件上传......
  • H5怎么实现文件断点续传
    这次给大家带来H5怎么实现文件断点续传,H5怎么文件断点续传的注意事项有哪些,下面就是实战案例,一起来看一下。HTML5的FILEapi,有一个slice方法,可以将BLOB对象进行分割。前端通过FileList对象获取到相应的文件,按照指定的分割方式将大文件分段,然后一段一段地传给后端,后端再按顺序一段......
  • vue项目中上传文件失败记录
    页面请求报【Failedtoloadresource:net::ERR_CONNECTION_ABORTED】错误,查了下是文件过大导致上传失败,element-ui本身没有大小限制,最后排查是Nginx默认是上传一个不能超过1M大小的文件#设置客户端传文件通过nginx大小client_max_body_size1024m;......
  • html5解决大文件断点续传
    一、使用fileapi对文件“切片”,使用slice断点续传思路:断点续传最核心的内容就是把文件“切片”然后再一片一片的传给服务器,但是这看似简单的上传过程却有着无数的坑。首先是文件的识别,一个文件被分成了若干份之后如何告诉服务器你切了多少块,以及最终服务器应该如何把你上传......
  • Html5大文件断点续传实现方法
    大文件分块一般常用的web服务器都有对向服务器端提交数据有大小限制。超越一定大小文件服务器端将返回拒绝信息。当然,web服务器都提供了配置文件可能修改限制的大小。针对ii实现大文件的上传网上也有一些通过修改web服务器限制文件大小来实现。不过这样对web服务器的平安带了问题......
  • HTML5中怎么实现文件断点续传功能
    HTML5中怎么实现文件断点续传功能,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。断点续传原理目前比较常用的断点续传的方法有两种,一种是通过websocket接口进行文件上传,另一种是通过ajax,两种方法各有......
  • 通过Java实现文件断点续传功能
    用户上传大文件,网络差点的需要历时数小时,万一线路中断,不具备断点续传的服务器就只能从头重传,而断点续传就是,允许用户从上传断线的地方继续传送,这样大大减少了用户的烦恼。本文将用Java语言实现断点续传,需要的可以参考一下什么是断点续传用户上传大文件,网络差点的需要历时数小......
  • 【文件上传漏洞】---基础(开始了解渗透流层)
    利用思路:常规类-扫描获取上传-会员中心上传-后台系统上传------各种途径上传CMS类(已知cms源码)编辑器类ckeditorfckeditorkindeditor------xxxeditor其他类/cve漏洞:解析漏洞cms漏洞其他编辑器/cve1@.什么是文件上传漏洞?有文件上传就有可能存在漏洞(主要......
  • 使用httpclient实现后台通过接口上传文件
    请求端:varupurl=Config.GetValue("fileupUrl");HttpPostedFilefile=files[0];MemoryStreammemoryStream=newMemoryStream();file.InputStream.CopyTo(memoryStream);byte[]fileBytes=memoryStream.ToArray();objectsendScObj......