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