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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,45 +1,45 @@ |
|||
using Microsoft.AspNetCore.Hosting; |
|||
using Microsoft.Extensions.Configuration; |
|||
using Microsoft.Extensions.Hosting; |
|||
using Serilog; |
|||
using System; |
|||
using System.IO; |
|||
|
|||
namespace LINGYUN.Platform |
|||
{ |
|||
public class Program |
|||
{ |
|||
public static int Main(string[] args) |
|||
{ |
|||
try |
|||
{ |
|||
var hostBuilder = CreateHostBuilder(args).Build(); |
|||
Log.Information("Starting Platform.Host."); |
|||
hostBuilder.Run(); |
|||
|
|||
return 0; |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
Log.Fatal(ex, "Host terminated unexpectedly!"); |
|||
return 1; |
|||
} |
|||
finally |
|||
{ |
|||
Log.CloseAndFlush(); |
|||
} |
|||
} |
|||
|
|||
internal static IHostBuilder CreateHostBuilder(string[] args) => |
|||
Host.CreateDefaultBuilder(args) |
|||
.ConfigureWebHostDefaults(webBuilder => |
|||
{ |
|||
webBuilder.UseStartup<Startup>(); |
|||
}) |
|||
.UseSerilog((context, provider, config) => |
|||
{ |
|||
config.ReadFrom.Configuration(context.Configuration); |
|||
}) |
|||
.UseAutofac(); |
|||
} |
|||
} |
|||
using Microsoft.AspNetCore.Hosting; |
|||
using Microsoft.Extensions.Configuration; |
|||
using Microsoft.Extensions.Hosting; |
|||
using Serilog; |
|||
using System; |
|||
using System.IO; |
|||
|
|||
namespace LINGYUN.Platform |
|||
{ |
|||
public class Program |
|||
{ |
|||
public static int Main(string[] args) |
|||
{ |
|||
try |
|||
{ |
|||
var hostBuilder = CreateHostBuilder(args).Build(); |
|||
Log.Information("Starting Platform.Host."); |
|||
hostBuilder.Run(); |
|||
|
|||
return 0; |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
Log.Fatal(ex, "Host terminated unexpectedly!"); |
|||
return 1; |
|||
} |
|||
finally |
|||
{ |
|||
Log.CloseAndFlush(); |
|||
} |
|||
} |
|||
|
|||
internal static IHostBuilder CreateHostBuilder(string[] args) => |
|||
Host.CreateDefaultBuilder(args) |
|||
.ConfigureWebHostDefaults(webBuilder => |
|||
{ |
|||
webBuilder.UseStartup<Startup>(); |
|||
}) |
|||
.UseSerilog((context, provider, config) => |
|||
{ |
|||
config.ReadFrom.Configuration(context.Configuration); |
|||
}) |
|||
.UseAutofac(); |
|||
} |
|||
} |
|||
|
|||
Loading…
Reference in new issue