.Net +Ajax大文件断点续传

发布时间 2023-12-20 10:04:38作者: Xproer-松鼠

什么是断点续传
大文件断点续传指的是在上传或下载大文件时,当传输中断或出现错误时,可以通过记录已经传输的数据和位置,下次从中断的位置继续传输,避免重新开始传输整个文件的过程,从而提高传输效率和稳定性。

实现思路
获取文件大小和已经传输的大小:在开始上传或下载文件之前,需要获取文件的总大小和已经传输的大小,以便于记录已经传输的数据和位置。

记录已经传输的数据和位置:在传输过程中,需要记录已经传输的数据和位置。通常可以将已经传输的数据写入到本地的缓存文件中,下次传输时从缓存文件中读取已经传输的数据和位置,然后从该位置继续传输。

实现断点续传:当传输中断或出现错误时,可以通过读取本地的缓存文件获取已经传输的数据和位置,然后从该位置继续传输,避免重新开始传输整个文件的过程。

检查传输完整性:在传输完成后,需要检查传输的完整性,以确保文件传输没有出现问题。通常可以通过比较传输的文件大小和本地文件的大小来检查传输的完整性

实际项目中需要考虑的细节和安全性问题
分块大小的选择:分块大小的选择应该考虑到上传速度和服务器处理能力,一般建议选择 1MB 到 4MB 的大小。

文件名的处理:在上传时应该对文件名进行编码,以避免文件名中包含非法字符导致的错误。

安全性:上传的文件可能包含恶意代码,因此需要对上传的文件进行校验和过滤,以确保上传的文件是安全的。可以对文件的扩展名、大小、MIME 类型等进行校验和过滤,或者使用一些成熟的第三方上传组件来提高安全性。

断点续传的处理:对于已经上传过的部分,需要对其进行检查和验证,以避免数据的重复和覆盖。可以在服务端保存已经上传的分块信息,以便于在续传时进行检查和验证。

并发上传的处理:并发上传可能会导致文件的不一致性,因此需要考虑并发上传时的锁机制,以保证上传的正确性和一致性。

上传进度的显示:在上传过程中,可以通过 Ajax 轮询或者 WebSocket 等技术实时显示上传进度,以提高用户体验。

文件上传的存储:在存储上传的文件时,应该将其存储在安全的位置,并且对文件的读写权限进行限制,以防止恶意访问和攻击。可以将文件存储在独立的服务器上,以提高安全性和可靠性。

上传速度的优化:上传大文件可能会消耗较长时间,可以通过使用分布式存储、CDN 加速等技术来优化上传速度和用户体验。

代码实现
<div>
<h1>大文件断点续传</h1>
<input type="file" id="fileUpload" />
<br /><br />
<button id="btnUpload">上传</button>
<br /><br />
<progress id="progressBar" value="0" max="100"></progress>
<br /><br />
<div id="status"></div>
<div><button id="stop">暂停/继续</button> </div>
</div>

<script type="text/javascript">
var file;
var chunkSize = 1024 * 1024; // 1MB
var startByte = 0;
var endByte = startByte + chunkSize;
var totalSize;
var loaded = 0;
var xhr;
var isStop = false;
$("#stop").click(function () {
if (file == null) return;
isStop = !isStop;
if (isStop == false) {

uploadChunk();
}
});

$("#fileUpload").change(function () {
file = this.files[0];
totalSize = file.size;
startByte = 0;
endByte = startByte + chunkSize;
});

$("#btnUpload").click(function () {
if (!file) {
alert("请选择文件.");
return;
}

xhr = new XMLHttpRequest();

xhr.upload.addEventListener("progress", function (e) {
loaded = startByte + e.loaded;
var percent = Math.floor((loaded / totalSize) * 100);
$("#progressBar").val(percent);
});

xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
$("#status").text("上传成功!");
}
else if (xhr.status === 206) {
if (loaded < totalSize) {
startByte = endByte;
endByte = startByte + chunkSize;
if (isStop == false) {
uploadChunk();
}
} else {
$("#status").text("上传成功!");
}
} else {
//失败后继续重试上传
if (isStop == false) {
uploadChunk();
}
}
}
};

endByte = startByte + chunkSize;
uploadChunk();
});

function uploadChunk() {
if (endByte > totalSize) {
endByte = totalSize;
}
xhr.open("POST", "/File/Upload");
xhr.setRequestHeader("Content-Type", "application/octet-stream");
xhr.setRequestHeader("X-File-Name", encodeURIComponent(file.name));
xhr.setRequestHeader("X-File-Size", totalSize);
xhr.setRequestHeader("X-Start-Byte", startByte);
xhr.setRequestHeader("X-End-Byte", endByte);
var slice = file.slice(startByte, endByte);
console.log(slice);
xhr.send(slice);
}
</script>


public class FileController : Controller
{

private const string UploadDirectory = "uploads";
[HttpPost]
public async Task<IActionResult> Upload()
{
// 获取上传文件的信息
var fileName = Request.Headers["X-File-Name"];
var fileSize = Convert.ToInt64(Request.Headers["X-File-Size"]);
var startByte = Convert.ToInt64(Request.Headers["X-Start-Byte"]);
var endByte = Convert.ToInt64(Request.Headers["X-End-Byte"]);

// 确定上传文件的保存路径
var uploadPath = Path.Combine(Directory.GetCurrentDirectory(), UploadDirectory);
if (!Directory.Exists(uploadPath))
{
Directory.CreateDirectory(uploadPath);
}

var filePath = Path.Combine(uploadPath, fileName);

// 检查是否为新文件
if (startByte == 0)
{
// 如果是新文件,则创建文件并保存数据
using (var fileStream = new FileStream(filePath, FileMode.Create))
{
await Request.Body.CopyToAsync(fileStream);
}
}
else
{
// 如果不是新文件,则在已有文件的基础上追加数据
using (var fileStream = new FileStream(filePath, FileMode.Append))
{
await Request.Body.CopyToAsync(fileStream);
}
}

// 检查上传进度
if (endByte >= fileSize - 1)
{
// 如果上传完成,则返回成功响应
return Ok();
}
else
{
// 如果未上传完成,则返回下一次上传的起始字节和终止字节
return StatusCode((int)HttpStatusCode.PartialContent, new
{
StartByte = endByte + 1,
EndByte = Math.Min(endByte + 1024 * 1024, fileSize - 1)
});
}
}
}

 

参考文章:http://blog.ncmem.com/wordpress/2023/12/20/net-ajax%e5%a4%a7%e6%96%87%e4%bb%b6%e6%96%ad%e7%82%b9%e7%bb%ad%e4%bc%a0/

欢迎入群一起讨论