15 changed files with 368 additions and 188 deletions
@ -0,0 +1,10 @@ |
|||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.OssManagement |
||||
|
{ |
||||
|
public interface IFileUploader |
||||
|
{ |
||||
|
Task UploadAsync(UploadFileChunkInput input, CancellationToken cancellationToken = default); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
using System.ComponentModel.DataAnnotations; |
||||
|
using System.IO; |
||||
|
using Volo.Abp.Auditing; |
||||
|
using Volo.Abp.Validation; |
||||
|
|
||||
|
namespace LINGYUN.Abp.OssManagement |
||||
|
{ |
||||
|
public class UploadFileChunkInput : UploadFile |
||||
|
{ |
||||
|
public string Bucket { get; set; } |
||||
|
public string Path { get; set; } |
||||
|
|
||||
|
#region 配合Uplaoder 分块传输
|
||||
|
/// <summary>
|
||||
|
/// 常规块大小
|
||||
|
/// </summary>
|
||||
|
[Required] |
||||
|
public long ChunkSize { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 当前块大小
|
||||
|
/// </summary>
|
||||
|
[Required] |
||||
|
public long CurrentChunkSize { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 当前上传中块的索引
|
||||
|
/// </summary>
|
||||
|
[Required] |
||||
|
public int ChunkNumber { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 块总数
|
||||
|
/// </summary>
|
||||
|
[Required] |
||||
|
public int TotalChunks { get; set; } |
||||
|
|
||||
|
#endregion
|
||||
|
|
||||
|
[DisableAuditing] |
||||
|
[DisableValidation] |
||||
|
public Stream Content { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,82 @@ |
|||||
|
using LINGYUN.Abp.Features.LimitValidation; |
||||
|
using LINGYUN.Abp.OssManagement.Features; |
||||
|
using LINGYUN.Abp.OssManagement.Localization; |
||||
|
using Microsoft.Extensions.Localization; |
||||
|
using System.Collections.Generic; |
||||
|
using System.ComponentModel.DataAnnotations; |
||||
|
using System.IO; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Features; |
||||
|
using Volo.Abp.Validation; |
||||
|
|
||||
|
namespace LINGYUN.Abp.OssManagement |
||||
|
{ |
||||
|
public class FileUploadMerger : ITransientDependency |
||||
|
{ |
||||
|
private readonly IFileValidater _fileValidater; |
||||
|
private readonly IOssContainerFactory _ossContainerFactory; |
||||
|
private readonly IStringLocalizer _stringLocalizer; |
||||
|
|
||||
|
public FileUploadMerger( |
||||
|
IFileValidater fileValidater, |
||||
|
IOssContainerFactory ossContainerFactory, |
||||
|
IStringLocalizer<AbpOssManagementResource> stringLocalizer) |
||||
|
{ |
||||
|
_fileValidater = fileValidater; |
||||
|
_ossContainerFactory = ossContainerFactory; |
||||
|
_stringLocalizer = stringLocalizer; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 合并文件
|
||||
|
/// </summary>
|
||||
|
/// <param name="input"></param>
|
||||
|
/// <returns></returns>
|
||||
|
[RequiresFeature(AbpOssManagementFeatureNames.OssObject.UploadFile)] |
||||
|
[RequiresLimitFeature( |
||||
|
AbpOssManagementFeatureNames.OssObject.UploadLimit, |
||||
|
AbpOssManagementFeatureNames.OssObject.UploadInterval, |
||||
|
LimitPolicy.Month)] |
||||
|
public virtual async Task<OssObject> MergeAsync(CreateOssObjectInput input) |
||||
|
{ |
||||
|
if (input.Content.IsNullOrEmpty()) |
||||
|
{ |
||||
|
ThrowValidationException(_stringLocalizer["FileNotBeNullOrEmpty"], "File"); |
||||
|
} |
||||
|
|
||||
|
await _fileValidater.ValidationAsync(new UploadFile |
||||
|
{ |
||||
|
TotalSize = input.Content.Length, |
||||
|
FileName = input.Object |
||||
|
}); |
||||
|
|
||||
|
var oss = CreateOssContainer(); |
||||
|
|
||||
|
var createOssObjectRequest = new CreateOssObjectRequest( |
||||
|
input.Bucket, |
||||
|
input.Object, |
||||
|
input.Content, |
||||
|
input.Path, |
||||
|
input.ExpirationTime) |
||||
|
{ |
||||
|
Overwrite = input.Overwrite |
||||
|
}; |
||||
|
return await oss.CreateObjectAsync(createOssObjectRequest); |
||||
|
} |
||||
|
|
||||
|
protected virtual IOssContainer CreateOssContainer() |
||||
|
{ |
||||
|
return _ossContainerFactory.Create(); |
||||
|
} |
||||
|
|
||||
|
private static void ThrowValidationException(string message, string memberName) |
||||
|
{ |
||||
|
throw new AbpValidationException(message, |
||||
|
new List<ValidationResult> |
||||
|
{ |
||||
|
new ValidationResult(message, new[] {memberName}) |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,100 @@ |
|||||
|
using System; |
||||
|
using System.IO; |
||||
|
using System.Linq; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.IO; |
||||
|
|
||||
|
namespace LINGYUN.Abp.OssManagement |
||||
|
{ |
||||
|
public class FileUploader : IFileUploader, ITransientDependency |
||||
|
{ |
||||
|
private readonly IFileValidater _fileValidater; |
||||
|
private readonly FileUploadMerger _fileUploadMerger; |
||||
|
|
||||
|
public FileUploader( |
||||
|
IFileValidater fileValidater, |
||||
|
FileUploadMerger fileUploadMerger) |
||||
|
{ |
||||
|
_fileValidater = fileValidater; |
||||
|
_fileUploadMerger = fileUploadMerger; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task UploadAsync(UploadFileChunkInput input, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
await _fileValidater.ValidationAsync(input); |
||||
|
// 以上传的文件名创建一个临时目录
|
||||
|
var tempFilePath = Path.Combine( |
||||
|
Path.GetTempPath(), |
||||
|
"lingyun-abp-application", |
||||
|
"oss-upload-tmp", |
||||
|
string.Concat(input.Path ?? "", input.FileName).ToMd5()); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
// 重新开始
|
||||
|
if (input.ChunkNumber == 1) |
||||
|
{ |
||||
|
DirectoryHelper.DeleteIfExists(tempFilePath); |
||||
|
} |
||||
|
|
||||
|
DirectoryHelper.CreateIfNotExists(tempFilePath); |
||||
|
|
||||
|
if (cancellationToken.IsCancellationRequested) |
||||
|
{ |
||||
|
// 如果取消请求,删除临时目录
|
||||
|
Directory.Delete(tempFilePath, true); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 以上传的分片索引创建临时文件
|
||||
|
var tempSavedFile = Path.Combine(tempFilePath, $"{input.ChunkNumber}.upd"); |
||||
|
if (input.Content != null) |
||||
|
{ |
||||
|
// 保存临时文件
|
||||
|
using var fs = new FileStream(tempSavedFile, FileMode.Create, FileAccess.Write); |
||||
|
// 写入当前分片文件
|
||||
|
await input.Content.CopyToAsync(fs); |
||||
|
} |
||||
|
|
||||
|
if (input.ChunkNumber == input.TotalChunks) |
||||
|
{ |
||||
|
var createOssObjectInput = new CreateOssObjectInput |
||||
|
{ |
||||
|
Bucket = input.Bucket, |
||||
|
Path = input.Path, |
||||
|
Object = input.FileName |
||||
|
}; |
||||
|
// 合并文件
|
||||
|
var mergeSavedFile = Path.Combine(tempFilePath, $"{input.FileName}"); |
||||
|
// 获取并排序所有分片文件
|
||||
|
var mergeFiles = Directory.GetFiles(tempFilePath).OrderBy(f => f.Length).ThenBy(f => f); |
||||
|
// 创建临时合并文件
|
||||
|
using var memoryStream = new MemoryStream(); |
||||
|
foreach (var mergeFile in mergeFiles) |
||||
|
{ |
||||
|
// 读取当前文件字节
|
||||
|
var mergeFileBytes = await FileHelper.ReadAllBytesAsync(mergeFile); |
||||
|
// 写入到合并文件流
|
||||
|
await memoryStream.WriteAsync(mergeFileBytes, 0, mergeFileBytes.Length, cancellationToken); |
||||
|
Array.Clear(mergeFileBytes, 0, mergeFileBytes.Length); |
||||
|
// 删除已参与合并的临时文件分片
|
||||
|
FileHelper.DeleteIfExists(mergeFile); |
||||
|
} |
||||
|
createOssObjectInput.SetContent(memoryStream); |
||||
|
// 分离出合并接口,合并时计算上传次数
|
||||
|
await _fileUploadMerger.MergeAsync(createOssObjectInput); |
||||
|
// 文件保存之后删除临时文件目录
|
||||
|
Directory.Delete(tempFilePath, true); |
||||
|
} |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
// 发生异常删除临时文件目录
|
||||
|
Directory.Delete(tempFilePath, true); |
||||
|
throw; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue