18 changed files with 559 additions and 7 deletions
@ -0,0 +1,36 @@ |
|||
using System; |
|||
using Volo.Abp.EventBus; |
|||
|
|||
namespace LINGYUN.Abp.OssManagement; |
|||
|
|||
/// <summary>
|
|||
/// 对象缓存确认信号
|
|||
/// </summary>
|
|||
[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; |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace LINGYUN.Abp.OssManagement; |
|||
public interface IOssObjectExpireor |
|||
{ |
|||
Task ExpireAsync(ExprieOssObjectRequest request); |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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<OssObjectAcknowledgeEto>, 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("/"); |
|||
} |
|||
} |
|||
@ -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<OssObjectTempCleanupService> Logger { get; set; } |
|||
public IOssObjectExpireor OssObjectExpireor { get; set; } |
|||
protected AbpOssManagementOptions Options { get; } |
|||
protected ICurrentTenant CurrentTenant { get; } |
|||
|
|||
public OssObjectTempCleanupService( |
|||
ICurrentTenant currentTenant, |
|||
IOptions<AbpOssManagementOptions> options) |
|||
{ |
|||
CurrentTenant = currentTenant; |
|||
Options = options.Value; |
|||
|
|||
OssObjectExpireor = NullOssObjectExpireor.Instance; |
|||
Logger = NullLogger<OssObjectTempCleanupService>.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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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<AbpOssManagementOptions> 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<OssObjectTempCleanupService>() |
|||
.CleanAsync(); |
|||
} |
|||
} |
|||
@ -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<List<TenantConfiguration>> GetTenantsAsync(); |
|||
} |
|||
@ -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<TenantConfigurationCacheItem> TenantCache { get; } |
|||
|
|||
public TenantConfigurationCache( |
|||
ITenantRepository tenantRepository, |
|||
IDistributedCache<TenantConfigurationCacheItem> tenantCache) |
|||
{ |
|||
TenantRepository = tenantRepository; |
|||
TenantCache = tenantCache; |
|||
} |
|||
|
|||
public async virtual Task<List<TenantConfiguration>> GetTenantsAsync() |
|||
{ |
|||
return (await GetForCacheItemAsync()).Tenants; |
|||
} |
|||
|
|||
protected async virtual Task<TenantConfigurationCacheItem> 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; |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
using System.Collections.Generic; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace LY.MicroService.PlatformManagement.MultiTenancy; |
|||
|
|||
[IgnoreMultiTenancy] |
|||
public class TenantConfigurationCacheItem |
|||
{ |
|||
public List<TenantConfiguration> Tenants { get; set; } |
|||
|
|||
public TenantConfigurationCacheItem() |
|||
{ |
|||
Tenants = new List<TenantConfiguration>(); |
|||
} |
|||
|
|||
public TenantConfigurationCacheItem(List<TenantConfiguration> tenants) |
|||
{ |
|||
Tenants = tenants; |
|||
} |
|||
} |
|||
Loading…
Reference in new issue