public class UploadFileService
{
private readonly CacheService _cache;
public UploadFileService(CacheService cache)
{
_cache = cache;
}
/// <summary>
/// 分片上传文件
/// </summary>
/// <param name="file"></param>
/// <param name="chunkNumber"></param>
/// <param name="totalChunks"></param>
/// <param name="fileName"></param>
/// <param name="filePath"></param>
/// <param name="fileHash"></param>
/// <returns></returns>
public async Task<FileBaseInfo?> UploadFile(IFormFile file, int chunkNumber, int totalChunks, string fileName, string filePath, string fileHash)
{
var tempPath = Path.Combine(filePath, $"{fileHash}.part{chunkNumber}");
using (var stream = new FileStream(tempPath, FileMode.Create, FileAccess.Write))
{
await file.CopyToAsync(stream);
}
var value = _cache.GetValue<bool[]>(fileHash);
if (value is null)
{
value = new bool[totalChunks];
}
value[chunkNumber] = true;
await _cache.SetValueAsync(fileHash, value, 60 * 60);
if (value.All(x => x))
{
var fileBaseInfo = await MergeFileChunks(fileName, totalChunks, filePath, fileHash);
await _cache.RemoveAsync(fileHash);
return projectFile;
}
return null;
}
/// <summary>
/// 合并文件
/// </summary>
/// <param name="fileName"></param>
/// <param name="totalChunks"></param>
/// <param name="filePath"></param>
/// <param name="fileHash"></param>
/// <returns></returns>
private async Task<FileBaseInfo> MergeFileChunks(string fileName, int totalChunks, string filePath, string fileHash)
{
var uploadPath = Path.Combine(filePath, fileName);
//文件大小统计
var size = 0L;
using (var destinationStream = new FileStream(uploadPath, FileMode.Create, FileAccess.Write))
{
for (int i = 0; i < totalChunks; i++)
{
var tempPath = Path.Combine(filePath, $"{fileHash}.part{i}");
using (var sourceStream = new FileStream(tempPath, FileMode.Open, FileAccess.Read))
{
await sourceStream.CopyToAsync(destinationStream);
}
System.IO.File.Delete(tempPath);
}
size = destinationStream.Length;
}
var fileBaseInfo = new FileBaseInfo()
{
FileFormat = fileName.Substring(fileName.LastIndexOf(".") + 1),
FileName = fileName,
FilePath = uploadPath,
Size = size,
};
return fileBaseInfo;
}
}
/// <summary>
/// 分片上传文件
/// </summary>
/// <param name="folderId"></param>
/// <param name="file"></param>
/// <param name="chunkNumber"></param>
/// <param name="totalChunks"></param>
/// <param name="fileName"></param>
/// <param name="fileHash"></param>
/// <returns></returns>
[HttpPost("[action]/{folderId}")]
public async Task<ActionResult> UploadFile([FromRoute] Guid folderId, IFormFile file, [FromForm] int chunkNumber, [FromForm] int totalChunks, [FromForm] string fileName, [FromForm] string fileHash)
{
if (file == null || chunkNumber < 0 || totalChunks <= 0 || string.IsNullOrEmpty(fileName))
{
return BadRequest("无效的请求参数。");
}
var folder = await _manager.FindAsync(folderId);
if (folder is null)
{
return NotFound("不存在文件夹");
}
var fileBaseInfo = await _uploadFileService.UploadFile(file, chunkNumber, totalChunks, fileName, folder.FolderPath, fileHash);
if (fileBaseInfo is not null)
{
fileBaseInfo.FolderId = folderId;
await _manager.CommandContext.AddAsync(fileBaseInfo);
await _manager.SaveChangesAsync();
}
return Ok();
}
/// <summary>
/// 查询分片上传到第几片
/// </summary>
/// <param name="fileHash"></param>
/// <returns></returns>
[HttpGet("upload/status")]
public ActionResult GetUploadStatus(string fileHash)
{
if (string.IsNullOrEmpty(fileHash))
{
return BadRequest("文件名不能为空");
}
var chunks = _cache.GetValue<bool[]>(fileHash);
if (chunks is not null)
{
return Ok(chunks.Select((uploaded, index) => new { ChunkNumber = index, Uploaded = uploaded }));
}
return Ok(null);
}
/// <summary>
/// 下载分析输入文件夹
/// </summary>
/// <param name="projectId"></param>
/// <param name="folderId"></param>
/// <returns></returns>
[HttpGet("DownAnalysisInputDir/{projectId}/{folderId}")]
public async Task<ActionResult> DownAnalysisInput([FromRoute] Guid projectId, [FromRoute] Guid folderId)
{
Response.Headers.ContentType = "application/zip";
await _manager.DownAnalysisInputDir(Response.BodyWriter.AsStream(), projectId, folderId);
await Response.CompleteAsync();
return Ok();
}
/// <summary>
/// 下载分析输入文件夹
/// </summary>
/// <param name="stream"></param>
/// <param name="projectId"></param>
/// <param name="folderId"></param>
/// <returns></returns>
public async Task DownAnalysisInputDir(Stream stream, Guid projectId, Guid folderId)
{
using var zipArchive = new ZipArchive(stream, ZipArchiveMode.Create, true);
var model = await GetAnalysisInputFolder(projectId, folderId);
await AddFilesAndFoldersToZipAsync(zipArchive, projectId, model, "");
}
/// <summary>
/// 递归目录添加到zip中
/// </summary>
/// <param name="archive"></param>
/// <param name="projectId"></param>
/// <param name="container"></param>
/// <param name="entryPrefix"></param>
/// <returns></returns>
private async Task AddFilesAndFoldersToZipAsync(ZipArchive archive, Guid projectId, AnalysisInputFolderDto container, string entryPrefix)
{
foreach (var folder in container.Folders)
{
var folderEntryPrefix = entryPrefix + folder.Name + "/";
var folderEntry = archive.CreateEntry(folderEntryPrefix);
var model = await GetAnalysisInputFolder(projectId, folder.Id);
await AddFilesAndFoldersToZipAsync(archive, projectId, model, folderEntryPrefix);
}
foreach (var file in container.Files)
{
using var fileStream = System.IO.File.Open(file.FilePath, FileMode.Open, FileAccess.Read);
using var entryStream = archive.CreateEntry(entryPrefix + file.FileName).Open();
await fileStream.CopyToAsync(entryStream);
}
}