diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Aliyun/LINGYUN/Abp/OssManagement/Aliyun/AbpOssManagementAliyunModule.cs b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Aliyun/LINGYUN/Abp/OssManagement/Aliyun/AbpOssManagementAliyunModule.cs index 4e62da63a..a9bafad1a 100644 --- a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Aliyun/LINGYUN/Abp/OssManagement/Aliyun/AbpOssManagementAliyunModule.cs +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Aliyun/LINGYUN/Abp/OssManagement/Aliyun/AbpOssManagementAliyunModule.cs @@ -1,5 +1,6 @@ using LINGYUN.Abp.BlobStoring.Aliyun; using Microsoft.Extensions.DependencyInjection; +using System; using Volo.Abp.Modularity; namespace LINGYUN.Abp.OssManagement.Aliyun @@ -12,6 +13,12 @@ namespace LINGYUN.Abp.OssManagement.Aliyun public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.AddTransient(); + + context.Services.AddTransient(provider => + provider + .GetRequiredService() + .Create() + .As()); } } } diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Aliyun/LINGYUN/Abp/OssManagement/Aliyun/AliyunOssContainer.cs b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Aliyun/LINGYUN/Abp/OssManagement/Aliyun/AliyunOssContainer.cs index 399b4b549..fff56230e 100644 --- a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Aliyun/LINGYUN/Abp/OssManagement/Aliyun/AliyunOssContainer.cs +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Aliyun/LINGYUN/Abp/OssManagement/Aliyun/AliyunOssContainer.cs @@ -13,7 +13,7 @@ namespace LINGYUN.Abp.OssManagement.Aliyun /// /// Oss容器的阿里云实现 /// - internal class AliyunOssContainer : IOssContainer + internal class AliyunOssContainer : IOssContainer, IOssObjectExpireor { protected ICurrentTenant CurrentTenant { get; } protected IOssClientFactory OssClientFactory { get; } @@ -136,6 +136,34 @@ namespace LINGYUN.Abp.OssManagement.Aliyun } } + public async virtual Task ExpireAsync(ExprieOssObjectRequest request) + { + var ossClient = await CreateClientAsync(); + + if (BucketExists(ossClient, request.Bucket)) + { + var listObjects = ossClient.ListObjects( + new ListObjectsRequest(request.Bucket) + { + MaxKeys = request.Batch + }); + + var removeKeys = new List(); + foreach (var ossObjectSummary in listObjects.ObjectSummaries) + { + if (ossObjectSummary.LastModified <= request.ExpirationTime) + { + removeKeys.Add(ossObjectSummary.Key); + } + } + + foreach (var removeKey in removeKeys) + { + ossClient.DeleteObject(listObjects.BucketName, removeKey); + } + } + } + public async virtual Task DeleteObjectAsync(GetOssObjectRequest request) { var ossClient = await CreateClientAsync(); diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Domain.Shared/LINGYUN/Abp/OssManagement/OssObjectAcknowledgeEto.cs b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Domain.Shared/LINGYUN/Abp/OssManagement/OssObjectAcknowledgeEto.cs new file mode 100644 index 000000000..214fdc596 --- /dev/null +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Domain.Shared/LINGYUN/Abp/OssManagement/OssObjectAcknowledgeEto.cs @@ -0,0 +1,36 @@ +using System; +using Volo.Abp.EventBus; + +namespace LINGYUN.Abp.OssManagement; + +/// +/// 对象缓存确认信号 +/// +[Serializable] +[EventName("abp.blob-storing.oss-object.ack")] +public class OssObjectAcknowledgeEto +{ + public string TempPath { get; set; } + public string Bucket { get; set; } + public string Path { get; set; } + public string Object { get; set; } + public TimeSpan? ExpirationTime { get; set; } + public OssObjectAcknowledgeEto() + { + + } + + public OssObjectAcknowledgeEto( + string tempPath, + string bucket, + string path, + string @object, + TimeSpan? expirationTime = null) + { + TempPath = tempPath; + Bucket = bucket; + Path = path; + Object = @object; + ExpirationTime = expirationTime; + } +} diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Domain/LINGYUN/Abp/OssManagement/AbpOssManagementOptions.cs b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Domain/LINGYUN/Abp/OssManagement/AbpOssManagementOptions.cs index c800e316c..c1f67c007 100644 --- a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Domain/LINGYUN/Abp/OssManagement/AbpOssManagementOptions.cs +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Domain/LINGYUN/Abp/OssManagement/AbpOssManagementOptions.cs @@ -1,6 +1,8 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using Volo.Abp; +using Volo.Abp.BackgroundWorkers; namespace LINGYUN.Abp.OssManagement { @@ -11,6 +13,31 @@ namespace LINGYUN.Abp.OssManagement /// 不允许被删除 /// public List StaticBuckets { get; } + /// + /// Default value: true. + /// If is false, + /// this property is ignored and the cleanup worker doesn't work for this application instance. + /// + public bool IsCleanupEnabled { get; set; } + /// + /// Default: 3,600,000 ms. + /// + public int CleanupPeriod { get; set; } + /// + /// 禁用缓存目录清除作业 + /// default: false + /// + public bool DisableTempPruning { get; set; } + /// + /// 每批次清理数量 + /// default: 100 + /// + public int MaximumTempSize { get; set; } + /// + /// 最小缓存对象寿命 + /// default: 30 minutes + /// + public TimeSpan MinimumTempLifeSpan { get; set; } public AbpOssManagementOptions() { @@ -25,8 +52,16 @@ namespace LINGYUN.Abp.OssManagement // 工作流 "workflow", // 图标 - "icons" + "icons", + // 缓存 + "temp" }; + + IsCleanupEnabled = true; + CleanupPeriod = 3_600_000; + MaximumTempSize = 100; + DisableTempPruning = false; + MinimumTempLifeSpan = TimeSpan.FromMinutes(30); } public void AddStaticBucket(string bucket) diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Domain/LINGYUN/Abp/OssManagement/ExprieOssObjectRequest.cs b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Domain/LINGYUN/Abp/OssManagement/ExprieOssObjectRequest.cs new file mode 100644 index 000000000..20864760f --- /dev/null +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Domain/LINGYUN/Abp/OssManagement/ExprieOssObjectRequest.cs @@ -0,0 +1,18 @@ +using System; + +namespace LINGYUN.Abp.OssManagement; +public class ExprieOssObjectRequest +{ + public string Bucket { get; } + public int Batch { get; } + public DateTimeOffset ExpirationTime { get; } + public ExprieOssObjectRequest( + string bucket, + int batch, + DateTimeOffset expirationTime) + { + Bucket = bucket; + Batch = batch; + ExpirationTime = expirationTime; + } +} diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Domain/LINGYUN/Abp/OssManagement/IOssObjectExpireor.cs b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Domain/LINGYUN/Abp/OssManagement/IOssObjectExpireor.cs new file mode 100644 index 000000000..dd2c5ebe2 --- /dev/null +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Domain/LINGYUN/Abp/OssManagement/IOssObjectExpireor.cs @@ -0,0 +1,7 @@ +using System.Threading.Tasks; + +namespace LINGYUN.Abp.OssManagement; +public interface IOssObjectExpireor +{ + Task ExpireAsync(ExprieOssObjectRequest request); +} diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Domain/LINGYUN/Abp/OssManagement/NullOssObjectExpireor.cs b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Domain/LINGYUN/Abp/OssManagement/NullOssObjectExpireor.cs new file mode 100644 index 000000000..e89a56747 --- /dev/null +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Domain/LINGYUN/Abp/OssManagement/NullOssObjectExpireor.cs @@ -0,0 +1,15 @@ +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.OssManagement; + +[Dependency(TryRegister = true)] +public class NullOssObjectExpireor : IOssObjectExpireor, ISingletonDependency +{ + public readonly static IOssObjectExpireor Instance = new NullOssObjectExpireor(); + + public Task ExpireAsync(ExprieOssObjectRequest request) + { + return Task.CompletedTask; + } +} diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Domain/LINGYUN/Abp/OssManagement/OssObjectAcknowledgeHandler.cs b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Domain/LINGYUN/Abp/OssManagement/OssObjectAcknowledgeHandler.cs new file mode 100644 index 000000000..4ecc1b761 --- /dev/null +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Domain/LINGYUN/Abp/OssManagement/OssObjectAcknowledgeHandler.cs @@ -0,0 +1,105 @@ +using System; +using System.Threading.Tasks; +using System.Web; +using Volo.Abp.DependencyInjection; +using Volo.Abp.EventBus.Distributed; + +namespace LINGYUN.Abp.OssManagement; + +public class OssObjectAcknowledgeHandler : IDistributedEventHandler, ITransientDependency +{ + protected const string Bucket = "temp"; + + private readonly IOssContainerFactory _containerFactory; + + public OssObjectAcknowledgeHandler(IOssContainerFactory containerFactory) + { + _containerFactory = containerFactory; + } + + public async virtual Task HandleEventAsync(OssObjectAcknowledgeEto eventData) + { + var ossContainer = _containerFactory.Create(); + + var tempPath = HttpUtility.UrlDecode(GetTempPath(eventData.TempPath)); + var tempObkect = HttpUtility.UrlDecode(GetTempObject(eventData.TempPath)); + + var ossObject = await ossContainer.GetObjectAsync(Bucket, tempObkect, tempPath, createPathIsNotExists: true); + + using (ossObject.Content) + { + var createOssObjectRequest = new CreateOssObjectRequest( + eventData.Bucket, + eventData.Object, + ossObject.Content, + eventData.Path, + eventData.ExpirationTime); + + await ossContainer.CreateObjectAsync(createOssObjectRequest); + } + } + + private static string GetTempPath(string tempPath) + { + // api/files/static/demo-tenant-id/temp/p/path/file.txt => path + if (tempPath.Contains(Bucket)) + { + // api/files/static/demo-tenant-id/temp/p/path/file.txt => p/path/file.txt + var lastIndex = tempPath.LastIndexOf(Bucket); + + tempPath = tempPath.Substring(lastIndex + Bucket.Length); + + // p/path/file.txt => 6 + var pathCharIndex = tempPath.LastIndexOf("/"); + if (pathCharIndex >= 0) + { + // p/path/file.txt => 0, 6 => + tempPath = tempPath.Substring(0, pathCharIndex); + } + } + + // 对目录url进行处理 + var pathIndex = tempPath.LastIndexOf("p/"); + if (pathIndex >= 0) + { + tempPath = tempPath.Substring(pathIndex + 2); + } + + // 尾部不是 / 符号则不是目录 + if (!tempPath.EndsWith("/")) + { + var pathCharIndex = tempPath.LastIndexOf("/"); + if (pathCharIndex >= 0) + { + // path/file.txt => 0, 4 => + tempPath = tempPath.Substring(0, pathCharIndex); + } + } + + return tempPath.RemovePreFix("/"); + } + + private static string GetTempObject(string tempPath) + { + // api/files/static/demo-tenant-id/temp/path/file.txt => file.txt + + var path = GetTempPath(tempPath); + + if (tempPath.EndsWith(path)) + { + var fileNameIndex = tempPath.LastIndexOf("/"); + if (fileNameIndex >= 0) + { + return tempPath.Substring(fileNameIndex + 1); + } + } + + var pathIndex = tempPath.LastIndexOf(path); + if (pathIndex >= 0) + { + tempPath = tempPath.Substring(pathIndex + path.Length); + } + + return tempPath.RemovePreFix("/"); + } +} diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Domain/LINGYUN/Abp/OssManagement/OssObjectTempCleanupService.cs b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Domain/LINGYUN/Abp/OssManagement/OssObjectTempCleanupService.cs new file mode 100644 index 000000000..979a6e904 --- /dev/null +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Domain/LINGYUN/Abp/OssManagement/OssObjectTempCleanupService.cs @@ -0,0 +1,56 @@ +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using System; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.MultiTenancy; + +namespace LINGYUN.Abp.OssManagement; + +public class OssObjectTempCleanupService : ITransientDependency +{ + public ILogger Logger { get; set; } + public IOssObjectExpireor OssObjectExpireor { get; set; } + protected AbpOssManagementOptions Options { get; } + protected ICurrentTenant CurrentTenant { get; } + + public OssObjectTempCleanupService( + ICurrentTenant currentTenant, + IOptions options) + { + CurrentTenant = currentTenant; + Options = options.Value; + + OssObjectExpireor = NullOssObjectExpireor.Instance; + Logger = NullLogger.Instance; + } + + public virtual async Task CleanAsync() + { + Logger.LogInformation("Start cleanup."); + + if (!Options.DisableTempPruning) + { + var host = CurrentTenant.IsAvailable ? CurrentTenant.Name ?? CurrentTenant.Id.ToString() : "host"; + + Logger.LogInformation($"Start cleanup {host} temp objects."); + + var threshold = DateTimeOffset.UtcNow - Options.MinimumTempLifeSpan; + + try + { + var request = new ExprieOssObjectRequest( + "temp", + Options.MaximumTempSize, + threshold); + + await OssObjectExpireor.ExpireAsync(request); + } + catch (Exception exception) + { + Logger.LogException(exception); + } + } + } +} diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.FileSystem/LINGYUN/Abp/OssManagement/FileSystem/AbpOssManagementFileSystemModule.cs b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.FileSystem/LINGYUN/Abp/OssManagement/FileSystem/AbpOssManagementFileSystemModule.cs index 936016ccf..03e87b134 100644 --- a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.FileSystem/LINGYUN/Abp/OssManagement/FileSystem/AbpOssManagementFileSystemModule.cs +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.FileSystem/LINGYUN/Abp/OssManagement/FileSystem/AbpOssManagementFileSystemModule.cs @@ -1,6 +1,5 @@ using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using Volo.Abp; +using System; using Volo.Abp.BlobStoring.FileSystem; using Volo.Abp.Modularity; @@ -14,6 +13,12 @@ namespace LINGYUN.Abp.OssManagement.FileSystem public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.AddTransient(); + + context.Services.AddTransient(provider => + provider + .GetRequiredService() + .Create() + .As()); } } } diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.FileSystem/LINGYUN/Abp/OssManagement/FileSystem/FileSystemOssContainer.cs b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.FileSystem/LINGYUN/Abp/OssManagement/FileSystem/FileSystemOssContainer.cs index a5c9fe3bd..5be44fcc2 100644 --- a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.FileSystem/LINGYUN/Abp/OssManagement/FileSystem/FileSystemOssContainer.cs +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.FileSystem/LINGYUN/Abp/OssManagement/FileSystem/FileSystemOssContainer.cs @@ -17,7 +17,7 @@ namespace LINGYUN.Abp.OssManagement.FileSystem /// /// Oss容器的本地文件系统实现 /// - internal class FileSystemOssContainer : IOssContainer + internal class FileSystemOssContainer : IOssContainer, IOssObjectExpireor { protected ICurrentTenant CurrentTenant { get; } protected IHostEnvironment Environment { get; } @@ -95,6 +95,39 @@ namespace LINGYUN.Abp.OssManagement.FileSystem return Task.FromResult(container); } + public virtual Task ExpireAsync(ExprieOssObjectRequest request) + { + var filePath = CalculateFilePath(request.Bucket); + + DirectoryHelper.CreateIfNotExists(filePath); + + // 目录也属于Oss对象,需要抽象的文件系统集合来存储 + var fileSystems = Directory.GetFileSystemEntries(filePath) + .Select(ConvertFileSystem) + .Where(file => file.CreationTime <= request.ExpirationTime) + .OrderBy(file => file.CreationTime) + .Take(request.Batch) + .Select(file => file.FullName); + + static FileSystemInfo ConvertFileSystem(string path) + { + if (File.Exists(path)) + { + return new FileInfo(path); + } + + return new DirectoryInfo(path); + } + + foreach (var fileSystem in fileSystems) + { + FileHelper.DeleteIfExists(fileSystem); + DirectoryHelper.DeleteIfExists(fileSystem, true); + } + + return Task.CompletedTask; + } + public async virtual Task CreateObjectAsync(CreateOssObjectRequest request) { var objectPath = !request.Path.IsNullOrWhiteSpace() diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Tencent/LINGYUN/Abp/OssManagement/Tencent/AbpOssManagementTencentModule.cs b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Tencent/LINGYUN/Abp/OssManagement/Tencent/AbpOssManagementTencentModule.cs index 8c84947e2..4fc93e7b5 100644 --- a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Tencent/LINGYUN/Abp/OssManagement/Tencent/AbpOssManagementTencentModule.cs +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Tencent/LINGYUN/Abp/OssManagement/Tencent/AbpOssManagementTencentModule.cs @@ -1,5 +1,6 @@ using LINGYUN.Abp.BlobStoring.Tencent; using Microsoft.Extensions.DependencyInjection; +using System; using Volo.Abp.Modularity; namespace LINGYUN.Abp.OssManagement.Tencent @@ -12,6 +13,12 @@ namespace LINGYUN.Abp.OssManagement.Tencent public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.AddTransient(); + + context.Services.AddTransient(provider => + provider + .GetRequiredService() + .Create() + .As()); } } } diff --git a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Tencent/LINGYUN/Abp/OssManagement/Tencent/TencentOssContainer.cs b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Tencent/LINGYUN/Abp/OssManagement/Tencent/TencentOssContainer.cs index f471665e2..1a001a1a9 100644 --- a/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Tencent/LINGYUN/Abp/OssManagement/Tencent/TencentOssContainer.cs +++ b/aspnet-core/modules/oss-management/LINGYUN.Abp.OssManagement.Tencent/LINGYUN/Abp/OssManagement/Tencent/TencentOssContainer.cs @@ -17,7 +17,7 @@ namespace LINGYUN.Abp.OssManagement.Tencent /// /// Oss容器的阿里云实现 /// - internal class TencentOssContainer : IOssContainer + internal class TencentOssContainer : IOssContainer, IOssObjectExpireor { protected IClock Clock { get; } protected ICurrentTenant CurrentTenant { get; } @@ -158,6 +158,33 @@ namespace LINGYUN.Abp.OssManagement.Tencent } } + public async virtual Task ExpireAsync(ExprieOssObjectRequest request) + { + var ossClient = await CreateClientAsync(); + + if (BucketExists(ossClient, request.Bucket)) + { + var getBucketRequest = new GetBucketRequest(request.Bucket); + + var getBucketResult = ossClient.GetBucket(getBucketRequest); + + var removeKeys = new List(); + foreach (var content in getBucketResult.listBucket.contentsList) + { + if (DateTime.TryParse(content.lastModified, out var lastModified) && lastModified <= request.ExpirationTime) + { + removeKeys.Add(content.key); + } + } + + foreach (var removeKey in removeKeys) + { + ossClient.DeleteObject( + new DeleteObjectRequest(getBucketResult.listBucket.name, removeKey)); + } + } + } + public async virtual Task DeleteObjectAsync(GetOssObjectRequest request) { var ossClient = await CreateClientAsync(); diff --git a/aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/BackgroundWorkers/OssObjectTempCleanupBackgroundWorker.cs b/aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/BackgroundWorkers/OssObjectTempCleanupBackgroundWorker.cs new file mode 100644 index 000000000..2369b3857 --- /dev/null +++ b/aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/BackgroundWorkers/OssObjectTempCleanupBackgroundWorker.cs @@ -0,0 +1,72 @@ +using LINGYUN.Abp.OssManagement; +using LY.MicroService.PlatformManagement.MultiTenancy; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System.Threading.Tasks; +using Volo.Abp.BackgroundWorkers; +using Volo.Abp.DistributedLocking; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Threading; + +namespace LY.MicroService.PlatformManagement.BackgroundWorkers; + +public class OssObjectTempCleanupBackgroundWorker : AsyncPeriodicBackgroundWorkerBase +{ + protected ICurrentTenant CurrentTenant { get; } + protected IAbpDistributedLock DistributedLock { get; } + protected ITenantConfigurationCache TenantConfigurationCache { get; } + + public OssObjectTempCleanupBackgroundWorker( + AbpAsyncTimer timer, + IServiceScopeFactory serviceScopeFactory, + IOptionsMonitor cleanupOptions, + IAbpDistributedLock distributedLock, + ICurrentTenant currentTenant, + ITenantConfigurationCache tenantConfigurationCache) + : base(timer, serviceScopeFactory) + { + CurrentTenant = currentTenant; + DistributedLock = distributedLock; + TenantConfigurationCache = tenantConfigurationCache; + timer.Period = cleanupOptions.CurrentValue.CleanupPeriod; + } + + protected async override Task DoWorkAsync(PeriodicBackgroundWorkerContext workerContext) + { + await using var handle = await DistributedLock.TryAcquireAsync(nameof(OssObjectTempCleanupBackgroundWorker)); + + Logger.LogInformation($"Lock is acquired for {nameof(OssObjectTempCleanupBackgroundWorker)}"); + + if (handle != null) + { + using (CurrentTenant.Change(null)) + { + await ExecuteCleanService(workerContext); + + var allActiveTenants = await TenantConfigurationCache.GetTenantsAsync(); + + foreach (var activeTenant in allActiveTenants) + { + using (CurrentTenant.Change(activeTenant.Id, activeTenant.Name)) + { + await ExecuteCleanService(workerContext); + } + } + } + + Logger.LogInformation($"Lock is released for {nameof(OssObjectTempCleanupBackgroundWorker)}"); + return; + } + + Logger.LogInformation($"Handle is null because of the locking for : {nameof(OssObjectTempCleanupBackgroundWorker)}"); + } + + private async static Task ExecuteCleanService(PeriodicBackgroundWorkerContext workerContext) + { + await workerContext + .ServiceProvider + .GetRequiredService() + .CleanAsync(); + } +} diff --git a/aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/MultiTenancy/ITenantConfigurationCache.cs b/aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/MultiTenancy/ITenantConfigurationCache.cs new file mode 100644 index 000000000..7b6736aa0 --- /dev/null +++ b/aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/MultiTenancy/ITenantConfigurationCache.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.MultiTenancy; + +namespace LY.MicroService.PlatformManagement.MultiTenancy; + +public interface ITenantConfigurationCache +{ + Task> GetTenantsAsync(); +} diff --git a/aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/MultiTenancy/TenantConfigurationCache.cs b/aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/MultiTenancy/TenantConfigurationCache.cs new file mode 100644 index 000000000..91d1e6fe1 --- /dev/null +++ b/aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/MultiTenancy/TenantConfigurationCache.cs @@ -0,0 +1,50 @@ +using LINGYUN.Abp.Saas.Tenants; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.Caching; +using Volo.Abp.DependencyInjection; +using Volo.Abp.MultiTenancy; + +namespace LY.MicroService.PlatformManagement.MultiTenancy; + +public class TenantConfigurationCache : ITenantConfigurationCache, ITransientDependency +{ + protected ITenantRepository TenantRepository { get; } + protected IDistributedCache TenantCache { get; } + + public TenantConfigurationCache( + ITenantRepository tenantRepository, + IDistributedCache tenantCache) + { + TenantRepository = tenantRepository; + TenantCache = tenantCache; + } + + public async virtual Task> GetTenantsAsync() + { + return (await GetForCacheItemAsync()).Tenants; + } + + protected async virtual Task GetForCacheItemAsync() + { + var cacheKey = "_Abp_Tenant_Configuration"; + var cacheItem = await TenantCache.GetAsync(cacheKey); + if (cacheItem == null) + { + var allActiveTenants = await TenantRepository.GetListAsync(); + + cacheItem = new TenantConfigurationCacheItem( + allActiveTenants + .Where(t => t.IsActive) + .Select(t => new TenantConfiguration(t.Id, t.Name) + { + IsActive = t.IsActive, + }).ToList()); + + await TenantCache.SetAsync(cacheKey, cacheItem); + } + + return cacheItem; + } +} diff --git a/aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/MultiTenancy/TenantConfigurationCacheItem.cs b/aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/MultiTenancy/TenantConfigurationCacheItem.cs new file mode 100644 index 000000000..7925c4aa7 --- /dev/null +++ b/aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/MultiTenancy/TenantConfigurationCacheItem.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using Volo.Abp.MultiTenancy; + +namespace LY.MicroService.PlatformManagement.MultiTenancy; + +[IgnoreMultiTenancy] +public class TenantConfigurationCacheItem +{ + public List Tenants { get; set; } + + public TenantConfigurationCacheItem() + { + Tenants = new List(); + } + + public TenantConfigurationCacheItem(List tenants) + { + Tenants = tenants; + } +} diff --git a/aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/PlatformManagementHttpApiHostModule.cs b/aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/PlatformManagementHttpApiHostModule.cs index 230c67c5c..e55316df9 100644 --- a/aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/PlatformManagementHttpApiHostModule.cs +++ b/aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/PlatformManagementHttpApiHostModule.cs @@ -24,15 +24,19 @@ using LINGYUN.Platform.EntityFrameworkCore; using LINGYUN.Platform.HttpApi; using LINGYUN.Platform.Theme.VueVbenAdmin; using LY.MicroService.Platform.EntityFrameworkCore; +using LY.MicroService.PlatformManagement.BackgroundWorkers; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; +using System.Threading.Tasks; using Volo.Abp; using Volo.Abp.AspNetCore.Authentication.JwtBearer; using Volo.Abp.AspNetCore.MultiTenancy; using Volo.Abp.AspNetCore.Serilog; using Volo.Abp.Autofac; +using Volo.Abp.BackgroundWorkers; using Volo.Abp.BlobStoring; using Volo.Abp.Caching.StackExchangeRedis; using Volo.Abp.FeatureManagement.EntityFrameworkCore; @@ -41,6 +45,7 @@ using Volo.Abp.Identity; using Volo.Abp.Modularity; using Volo.Abp.PermissionManagement.EntityFrameworkCore; using Volo.Abp.SettingManagement.EntityFrameworkCore; +using Volo.Abp.Threading; namespace LY.MicroService.PlatformManagement; @@ -119,6 +124,22 @@ public partial class PlatformManagementHttpApiHostModule : AbpModule ConfigureSecurity(context.Services, configuration, hostingEnvironment.IsDevelopment()); } + public override void OnPostApplicationInitialization(ApplicationInitializationContext context) + { + AsyncHelper.RunSync(() => OnPostApplicationInitializationAsync(context)); + } + + public async override Task OnPostApplicationInitializationAsync(ApplicationInitializationContext context) + { + var options = context.ServiceProvider.GetRequiredService>().Value; + if (options.IsCleanupEnabled) + { + await context.ServiceProvider + .GetRequiredService() + .AddAsync(context.ServiceProvider.GetRequiredService()); + } + } + public override void OnApplicationInitialization(ApplicationInitializationContext context) { var app = context.GetApplicationBuilder();