文件分片上传
分片上传:
1、前端(vue2+elementui)
<template>
<div>
<el-upload
:http-request="handleUpload"
:before-upload="beforeUpload"
multiple
:auto-upload="false"
:on-change="handleFileChange"
>
<el-button slot="trigger" type="primary">选择文件</el-button>
<el-button type="success" @click="submitUpload" :disabled="!file">上传</el-button>
</el-upload>
<el-progress v-if="progress > 0" :percentage="progress" status="success"></el-progress>
</div>
</template>
<script>
export default {
data() {
return {
file: null, // 当前文件
progress: 0, // 上传进度
chunkSize: 5 * 1024 * 1024, // 每个分片大小 5MB
};
},
methods: {
// 文件选择回调
handleFileChange(file) {
this.file = file.raw;
},
// 上传前检查
beforeUpload(file) {
if (file.type !== "application/pdf") {
this.$message.error("只能上传 PDF 文件!");
return false;
}
if (file.size > 50 * 1024 * 1024) {
this.$message.error("文件大小不能超过 50MB!");
return false;
}
return true;
},
// 分片上传逻辑
async handleUpload(option) {
const file = option.file;
const totalChunks = Math.ceil(file.size / this.chunkSize);
let uploadedChunks = 0;
for (let i = 0; i < totalChunks; i++) {
const start = i * this.chunkSize;
const end = Math.min(file.size, start + this.chunkSize);
const chunk = file.slice(start, end);
const formData = new FormData();
formData.append("chunk", chunk);
formData.append("fileName", file.name);
formData.append("chunkIndex", i);
formData.append("totalChunks", totalChunks);
try {
await this.$axios.post("/api/upload/chunk", formData);
uploadedChunks++;
this.progress = Math.round((uploadedChunks / totalChunks) * 100);
} catch (error) {
this.$message.error("上传失败,请重试!");
console.error(error);
return;
}
}
// 通知服务器合并文件
try {
await this.$axios.post("/api/upload/merge", { fileName: file.name, totalChunks });
this.$message.success("文件上传成功!");
} catch (error) {
this.$message.error("文件合并失败!");
console.error(error);
}
},
submitUpload() {
if (this.file) {
this.handleUpload({ file: this.file });
} else {
this.$message.error("请选择文件!");
}
},
},
};
</script>
后端(.net6 webApi)
- 分片上传接口
用于保存文件分片到临时目录:
[HttpPost("chunk")]
public async Task<IActionResult> UploadChunk([FromForm] IFormFile chunk, [FromForm] string fileName, [FromForm] int chunkIndex, [FromForm] int totalChunks)
{
try
{
var tempFolder = Path.Combine("TempChunks", fileName);
if (!Directory.Exists(tempFolder))
Directory.CreateDirectory(tempFolder);
var chunkPath = Path.Combine(tempFolder, $"{chunkIndex}");
using (var stream = new FileStream(chunkPath, FileMode.Create))
{
await chunk.CopyToAsync(stream);
}
return Ok(new { success = true });
}
catch (Exception ex)
{
return BadRequest(new { success = false, message = ex.Message });
}
}
- 文件合并接口
用于将所有分片合并为一个完整文件:
[HttpPost("merge")]
public async Task<IActionResult> MergeFile([FromBody] MergeRequest mergeRequest)
{
try
{
var tempFolder = Path.Combine("TempChunks", mergeRequest.FileName);
if (!Directory.Exists(tempFolder))
return BadRequest(new { success = false, message = "分片目录不存在" });
var finalFolder = Path.Combine("Uploads");
if (!Directory.Exists(finalFolder))
Directory.CreateDirectory(finalFolder);
var finalFilePath = Path.Combine(finalFolder, mergeRequest.FileName);
using (var finalFileStream = new FileStream(finalFilePath, FileMode.Create))
{
for (int i = 0; i < mergeRequest.TotalChunks; i++)
{
var chunkPath = Path.Combine(tempFolder, $"{i}");
if (!System.IO.File.Exists(chunkPath))
return BadRequest(new { success = false, message = $"缺少分片 {i}" });
var chunkData = await System.IO.File.ReadAllBytesAsync(chunkPath);
await finalFileStream.WriteAsync(chunkData, 0, chunkData.Length);
System.IO.File.Delete(chunkPath); // 删除已合并的分片
}
}
Directory.Delete(tempFolder); // 删除临时目录
return Ok(new { success = true, path = $"/Uploads/{mergeRequest.FileName}" });
}
catch (Exception ex)
{
return BadRequest(new { success = false, message = ex.Message });
}
}
public class MergeRequest
{
public string FileName { get; set; }
public int TotalChunks { get; set; }
}