一、背景与痛点
在传统 Web 应用中,文件上传看似简单,但一旦遇到以下问题,就会变得棘手:
- 大文件上传超时
- IIS / Kestrel 请求体大小限制
- 网络中断导致前功尽弃
- 上传进度无法感知
- 服务器内存占用过高
因此,一个生产级文件上传方案,通常需要满足:
✅ 支持大文件
✅ 断点续传
✅ 分片上传
✅ 上传进度
✅ 安全可控
✅ 易于扩展
本文将基于 ASP.NET Core 给出一套完整可落地的解决方案。
二、整体架构设计
技术选型
|
层级 |
技术 |
|---|---|
|
后端 |
ASP.NET Core Minimal API / Controller |
|
前端 |
HTML + JavaScript(Fetch / Axios) |
|
存储 |
本地磁盘 / 对象存储(MinIO、OSS、COS) |
|
并发控制 |
Task + Semaphore |
|
校验机制 |
MD5 / SHA256 |
|
断点续传 |
分片 + 唯一文件标识 |
核心流程(分片上传)
客户端 ├─ 计算文件 Hash ├─ 检查是否已上传 ├─ 按大小切分文件(如 5MB) ├─ 并行上传分片 └─ 通知服务端合并 服务端 ├─ 接收分片并落盘 ├─ 记录分片状态 ├─ 校验完整性 └─ 合并文件
三、基础配置(非常重要)
1️⃣ 关闭模型绑定限制
// Program.cs
builder.Services.Configure(options =>
{
options.MultipartBodyLengthLimit = long.MaxValue;
});
builder.WebHost.ConfigureKestrel(serverOptions =>
{
serverOptions.Limits.MaxRequestBodySize = null; // 不限制
});
⚠️ 若使用 IIS,还需修改
web.config
四、普通小文件上传(快速版)
Controller 示例
[ApiController]
[Route("api/upload")]
public class UploadController : ControllerBase
{
private readonly IWebHostEnvironment _env;
public UploadController(IWebHostEnvironment env)
{
_env = env;
}
[HttpPost("single")]
public async Task SingleFile(IFormFile file)
{
if (file == null || file.Length == 0)
return BadRequest("文件为空");
var savePath = Path.Combine(_env.WebRootPath, "uploads");
Directory.CreateDirectory(savePath);
var fileName = $"{Guid.NewGuid()}{Path.GetExtension(file.FileName)}";
var filePath = Path.Combine(savePath, fileName);
await using var stream = System.IO.File.Create(filePath);
await file.CopyToAsync(stream);
return Ok(new { fileName, filePath });
}
}
前端示例
async function upload() {
const file = document.getElementById('file').files[0];
const formData = new FormData();
formData.append("file", file);
await fetch("/api/upload/single", {
method: "POST",
body: formData
});
}
⚠️ 不适合大文件,仅用于演示基础能力。
五、大文件分片上传(核心方案)
分片规则设计
|
参数 |
说明 |
|---|---|
|
chunk |
当前分片索引 |
|
chunks |
总分片数 |
|
fileHash |
文件唯一标识 |
|
chunkHash |
分片标识 |
|
fileName |
文件名 |
六、前端分片上传实现
计算文件 Hash(SparkMD5)
async function calculateHash(file) {
return new Promise(resolve => {
const spark = new SparkMD5.ArrayBuffer();
const reader = new FileReader();
reader.onload = e => {
spark.append(e.target.result);
resolve(spark.end());
};
reader.readAsArrayBuffer(file);
});
}
分片上传核心逻辑
const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB
async function uploadFile(file) {
const fileHash = await calculateHash(file);
const chunks = Math.ceil(file.size / CHUNK_SIZE);
for (let i = 0; i
七、后端分片接收接口
保存分片
[HttpPost("chunk")]
public async Task UploadChunk(
IFormFile chunk,
[FromForm] string fileHash,
[FromForm] int chunkIndex,
[FromForm] int totalChunks)
{
var chunkDir = Path.Combine(_env.WebRootPath, "chunks", fileHash);
Directory.CreateDirectory(chunkDir);
var chunkPath = Path.Combine(chunkDir, chunkIndex.ToString());
await using var stream = System.IO.File.Create(chunkPath);
await chunk.CopyToAsync(stream);
return Ok();
}
八、分片合并接口
[HttpPost("merge")]
public IActionResult MergeFiles([FromBody] MergeRequest request)
{
var chunkDir = Path.Combine(_env.WebRootPath, "chunks", request.FileHash);
var outputPath = Path.Combine(_env.WebRootPath, "uploads", request.FileName);
using var outputStream = System.IO.File.Create(outputPath);
for (int i = 0; i
九、断点续传(秒传优化)
已上传分片查询
[HttpGet("uploaded-chunks")]
public IActionResult GetUploadedChunks(string fileHash)
{
var chunkDir = Path.Combine(_env.WebRootPath, "chunks", fileHash);
if (!Directory.Exists(chunkDir))
return Ok(Array.Empty());
var uploaded = Directory.GetFiles(chunkDir)
.Select(f => int.Parse(Path.GetFileName(f)))
.OrderBy(x => x)
.ToList();
return Ok(uploaded);
}
前端跳过已上传分片
const uploaded = await (await fetch(`/api/upload/uploaded-chunks?fileHash=${fileHash}`)).json();
for (let i = 0; i
十、并发控制(防止压垮服务器)
private static readonly SemaphoreSlim _semaphore = new(4);
[HttpPost("chunk")]
public async Task UploadChunk(...)
{
await _semaphore.WaitAsync();
try
{
// 保存分片逻辑
}
finally
{
_semaphore.Release();
}
}
十一、安全性建议(生产必做)
✅ 文件类型白名单
✅ 文件大小上限
✅ 用户鉴权
✅ 防重放攻击(Token / 签名)
✅ 病毒扫描(ClamAV)
✅ 分片过期清理(定时任务)
var ext = Path.GetExtension(fileName).ToLower();
if (!new[] { ".jpg", ".png", ".pdf" }.Contains(ext))
return BadRequest("不支持的文件类型");
十二、进阶优化方向
|
功能 |
方案 |
|---|---|
|
极速秒传 |
文件 Hash 查重 |
|
分布式存储 |
MinIO / OSS |
|
上传进度 |
WebSocket / SSE |
|
大流量 |
CDN + 直传 OSS |
|
可观测性 |
Prometheus + Grafana |
十三、总结
本文提供了一套 从零到生产可用的 .NET 文件上传方案:
- ✅ 支持超大文件
- ✅ 分片 + 断点续传
- ✅ 并发可控
- ✅ 易扩展、易维护
- ✅ 适合企业级项目
一句话总结:
小文件直接传,大文件必须分片,生产环境一定要做限流与安全校验。
到此这篇关于.NET 实现文件上传与大文件分片上传完整解决方案的文章就介绍到这了,更多相关.net文件上传与大文件分片上传内容请搜索IT俱乐部以前的文章或继续浏览下面的相关文章希望大家以后多多支持IT俱乐部!
