在航空航天等对数据可靠性要求极高的领域,大文件上传一直是系统设计的难点。传统单机上传方案存在诸多限制:网络波动导致传输中断、服务器内存溢出、缺乏断点续传能力等。本文将分享一套基于.NET Core的分布式任务队列架构,实现高可靠的分片上传方案。
核心设计目标:支持GB级工程图纸/遥感影像上传,断点续传精度达1MB,集群环境下任务状态可共享。
我们采用分层架构设计:
与示例中Bestcomy组件相比,本方案具有三大优势:
首先在Startup.cs中配置服务:
csharp复制public void ConfigureServices(IServiceCollection services)
{
services.AddHangfire(config =>
config.UseRedisStorage(Configuration.GetConnectionString("Redis")));
services.AddSingleton<IFileStorage>(provider =>
new HybridStorage(
localPath: Path.Combine(env.ContentRootPath, "UploadCache"),
cloudService: new S3Adapter(Configuration["AWS:Bucket"])
));
}
关键参数说明:
前端需实现分片逻辑(以JavaScript为例):
javascript复制async function uploadChunk(file, chunkIndex) {
const chunkSize = 5 * 1024 * 1024; // 5MB分片
const start = chunkIndex * chunkSize;
const end = Math.min(file.size, start + chunkSize);
const chunk = file.slice(start, end);
const formData = new FormData();
formData.append('fileId', generateFileId(file));
formData.append('chunkIndex', chunkIndex);
formData.append('totalChunks', Math.ceil(file.size / chunkSize));
formData.append('chunk', chunk);
return await fetch('/api/upload/chunk', {
method: 'POST',
body: formData
});
}
后端控制器处理逻辑:
csharp复制[HttpPost("chunk")]
public async Task<IActionResult> UploadChunk(
[FromForm] string fileId,
[FromForm] int chunkIndex,
[FromForm] IFormFile chunk)
{
using (var stream = chunk.OpenReadStream())
{
await _storage.SaveChunk(fileId, chunkIndex, stream);
}
var progress = await _storage.GetProgress(fileId);
if (progress.IsCompleted)
{
BackgroundJob.Enqueue<FileMerger>(x =>
x.MergeAsync(fileId, progress.TotalChunks));
}
return Ok(new { progress });
}
关键细节:每个分片MD5校验通过后才写入临时目录,合并时进行二次校验。
文件合并任务通过Hangfire分发:
csharp复制public class FileMerger
{
private readonly IFileStorage _storage;
public async Task MergeAsync(string fileId, int totalChunks)
{
using (var redLock = new RedLock("merge:" + fileId, TimeSpan.FromSeconds(30)))
{
if (!redLock.IsAcquired)
throw new Exception("获取分布式锁失败");
var tempPaths = new List<string>();
for (int i = 0; i < totalChunks; i++)
{
tempPaths.Add(await _storage.GetChunkPath(fileId, i));
}
await using (var destStream = _storage.CreateDestinationFile(fileId))
{
foreach (var path in tempPaths.OrderBy(p => p))
{
await using (var srcStream = File.OpenRead(path))
{
await srcStream.CopyToAsync(destStream);
}
File.Delete(path); // 删除已合并分片
}
}
}
}
}
并发控制要点:
通过流式处理避免内存溢出:
csharp复制public async Task SaveChunk(string fileId, int chunkIndex, Stream stream)
{
var chunkPath = GetChunkPath(fileId, chunkIndex);
await using (var fileStream = new FileStream(
chunkPath,
FileMode.Create,
FileAccess.Write,
FileShare.None,
bufferSize: 81920, // 最佳缓冲区大小
FileOptions.Asynchronous | FileOptions.SequentialScan))
{
await stream.CopyToAsync(fileStream);
}
}
参数调优建议:
前端记录上传状态:
javascript复制class UploadState {
constructor(file) {
this.fileId = generateFileId(file);
this.chunkStatus = new Array(Math.ceil(file.size / CHUNK_SIZE)).fill(false);
}
async recover() {
const res = await fetch(`/api/upload/progress?fileId=${this.fileId}`);
const { uploadedChunks } = await res.json();
uploadedChunks.forEach(i => this.chunkStatus[i] = true);
}
}
服务端状态查询接口:
csharp复制[HttpGet("progress")]
public async Task<IActionResult> GetProgress(string fileId)
{
var chunks = await _storage.ListChunks(fileId);
return Ok(new {
uploadedChunks = chunks.Select(c => c.Index).ToArray(),
totalSize = chunks.Sum(c => c.Size)
});
}
案例1:分片合并后文件MD5校验失败
案例2:Redis锁超时导致合并中断
在Prometheus中配置关键指标:
yaml复制metrics:
upload_chunk_duration: histogram[10ms, 50ms, 100ms, 500ms]
merge_job_duration: histogram[1s, 5s, 10s, 30s]
redis_lock_wait: gauge
告警规则示例:
提供NuGet包简化集成:
powershell复制Install-Package Aerospace.UploadSDK -Version 1.2.0
核心调用方式:
csharp复制var client = new UploadClient(new UploadConfig {
Endpoint = "https://api.yourdomain.com",
ChunkSize = 8 * 1024 * 1024 // 8MB
});
await client.UploadFileAsync(
filePath: "design.sldprt",
progress: p => Console.WriteLine($"{p:P}"));
实现JWT签名验证:
csharp复制[AttributeUsage(AttributeTargets.Method)]
public class ChunkSignatureAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
var request = context.HttpContext.Request;
var signature = request.Headers["X-Chunk-Sign"];
using (var hmac = new HMACSHA256(_config.SecretKey))
{
var hash = hmac.ComputeHash(request.Body);
if (!hash.SequenceEqual(Convert.FromBase64String(signature)))
{
context.Result = new UnauthorizedResult();
}
}
}
}
在航空航天这类对数据完整性要求极高的场景,我们还需要实现: