在c#开发中处理大文件上传时,直接传输完整文件往往会遇到请求超时、服务器内存溢出、网络波动导致上传失败需要重传等问题,分片上传将大文件切割为多个小分片依次传输,能有效避免上述问题,同时支持断点续传提升上传体验。

大文件分片上传核心流程
完整的c#大文件分片上传流程分为四个核心步骤:
- 前端将大文件按照固定大小切割为多个分片,为每个分片标记唯一标识、分片索引、总分片数等信息
- 前端依次调用后端接口上传每个分片,后端接收后将分片临时存储到服务器指定目录
- 前端在所有分片上传完成后,调用后端合并接口通知服务器合并所有分片
- 后端校验所有分片完整性后,将临时分片合并为完整文件,清理临时分片数据
前端分片切割实现
前端使用JavaScript的File API实现文件分片切割,将切割后的分片通过FormData传递给后端,以下是示例代码:
// 定义分片大小,这里设置为2MB
const CHUNK_SIZE = 2 * 1024 * 1024;
// 要上传的大文件对象
const file = document.getElementById('fileInput').files[0];
// 生成文件唯一标识,避免同名文件冲突
const fileId = Date.now() + '_' + file.name;
// 计算总分片数
const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
// 循环切割分片并上传
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);
// 创建FormData携带分片信息
const formData = new FormData();
formData.append('fileId', fileId);
formData.append('chunkIndex', i);
formData.append('totalChunks', totalChunks);
formData.append('fileName', file.name);
formData.append('chunk', chunk);
// 调用上传接口,这里使用fetch发送请求
fetch('/api/upload/chunk', {
method: 'POST',
body: formData
}).then(res => res.json())
.then(data => {
if (data.success) {
console.log(`第${i+1}个分片上传成功`);
// 所有分片上传完成后调用合并接口
if (i === totalChunks - 1) {
mergeChunks(fileId, file.name, totalChunks);
}
}
});
}
// 合并分片请求方法
function mergeChunks(fileId, fileName, totalChunks) {
fetch('/api/upload/merge', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
fileId: fileId,
fileName: fileName,
totalChunks: totalChunks
})
}).then(res => res.json())
.then(data => {
if (data.success) {
console.log('文件合并成功,完整文件路径:' + data.filePath);
}
});
}
后端分片接收接口实现
后端使用ASP.NET Core Web API实现分片接收逻辑,接收前端传递的分片数据后,将分片临时存储到服务器的临时目录中,分片命名规则为文件标识_分片索引.tmp,方便后续合并时识别顺序。
using Microsoft.AspNetCore.Mvc;
using System.IO;
[ApiController]
[Route("api/upload")]
public class UploadController : ControllerBase
{
// 临时分片存储目录,实际开发中可根据需求调整路径
private readonly string _tempChunkDir = Path.Combine(Directory.GetCurrentDirectory(), "temp_chunks");
// 完整文件存储目录
private readonly string _finalFileDir = Path.Combine(Directory.GetCurrentDirectory(), "upload_files");
public UploadController()
{
// 初始化目录,如果不存在则创建
if (!Directory.Exists(_tempChunkDir))
{
Directory.CreateDirectory(_tempChunkDir);
}
if (!Directory.Exists(_finalFileDir))
{
Directory.CreateDirectory(_finalFileDir);
}
}
/// <summary>
/// 接收单个分片上传的接口
/// </summary>
[HttpPost("chunk")]
public IActionResult UploadChunk([FromForm] string fileId, [FromForm] int chunkIndex, [FromForm] int totalChunks, [FromForm] string fileName, IFormFile chunk)
{
if (chunk == null || chunk.Length == 0)
{
return BadRequest(new { success = false, message = "分片数据为空" });
}
try
{
// 临时分片文件路径,格式为 文件标识_分片索引.tmp
string chunkFilePath = Path.Combine(_tempChunkDir, $"{fileId}_{chunkIndex}.tmp");
// 保存分片到临时目录
using (var fileStream = new FileStream(chunkFilePath, FileMode.Create))
{
chunk.CopyTo(fileStream);
}
return Ok(new { success = true, message = "分片上传成功" });
}
catch (Exception ex)
{
return StatusCode(500, new { success = false, message = $"分片上传失败:{ex.Message}" });
}
}
}
后端分片合并接口实现
所有分片上传完成后,前端调用合并接口,后端校验所有分片是否存在,然后按照分片索引顺序将分片合并为完整文件,合并完成后清理临时分片数据。
/// <summary>
/// 合并所有分片的接口
/// </summary>
[HttpPost("merge")]
public IActionResult MergeChunks([FromBody] MergeRequest request)
{
try
{
// 校验所有分片是否都存在
for (int i = 0; i < request.TotalChunks; i++)
{
string chunkFilePath = Path.Combine(_tempChunkDir, $"{request.FileId}_{i}.tmp");
if (!System.IO.File.Exists(chunkFilePath))
{
return BadRequest(new { success = false, message = $"第{i+1}个分片不存在,无法合并" });
}
}
// 完整文件保存路径,避免同名文件覆盖,添加时间戳
string finalFileName = $"{DateTime.Now:yyyyMMddHHmmss}_{request.FileName}";
string finalFilePath = Path.Combine(_finalFileDir, finalFileName);
// 按照分片索引顺序合并文件
using (var finalStream = new FileStream(finalFilePath, FileMode.Create))
{
for (int i = 0; i < request.TotalChunks; i++)
{
string chunkFilePath = Path.Combine(_tempChunkDir, $"{request.FileId}_{i}.tmp");
using (var chunkStream = new FileStream(chunkFilePath, FileMode.Open))
{
chunkStream.CopyTo(finalStream);
}
// 合并完成后删除临时分片
System.IO.File.Delete(chunkFilePath);
}
}
return Ok(new { success = true, filePath = $"/upload_files/{finalFileName}" });
}
catch (Exception ex)
{
return StatusCode(500, new { success = false, message = $"文件合并失败:{ex.Message}" });
}
}
/// <summary>
/// 合并请求的参数模型
/// </summary>
public class MergeRequest
{
public string FileId { get; set; }
public string FileName { get; set; }
public int TotalChunks { get; set; }
}
}
断点续传实现思路
要实现断点续传,只需要在上传分片前,先调用后端接口查询已经上传成功的分片索引列表,前端跳过已上传的分片即可。后端新增查询已上传分片的接口:
/// <summary>
/// 查询已上传的分片索引列表,用于断点续传
/// </summary>
[HttpGet("uploaded_chunks")]
public IActionResult GetUploadedChunks([FromQuery] string fileId, [FromQuery] int totalChunks)
{
List<int> uploadedChunks = new List<int>();
for (int i = 0; i < totalChunks; i++)
{
string chunkFilePath = Path.Combine(_tempChunkDir, $"{fileId}_{i}.tmp");
if (System.IO.File.Exists(chunkFilePath))
{
uploadedChunks.Add(i);
}
}
return Ok(new { success = true, uploadedChunks = uploadedChunks });
}
前端在上传前先调用这个接口获取已上传的分片列表,循环中跳过这些索引对应的分片即可实现断点续传。
注意事项
- 分片大小建议设置为1MB到5MB之间,太小会增加请求次数,太大会失去分片上传的意义,可根据实际网络情况调整
- 临时分片目录需要定期清理,避免服务器磁盘被无用临时文件占满,可以设置定时任务删除超过24小时的临时分片
- 合并文件时需要校验分片的完整性,可以通过给每个分片添加MD5校验值,合并前校验分片MD5是否匹配,避免分片损坏导致文件不可用
- 如果上传的是超大文件,建议对合并后的文件也做MD5校验,确保最终文件与原始文件一致
- 接口需要做好权限校验,避免未授权用户上传文件占用服务器资源
C#大文件分片上传FileStreamMultipartFormDataContent分片合并修改时间:2026-06-30 06:39:50