Browse Source

feat(localization): regularly refresh the localized static cache

pull/1305/head
colin 8 months ago
parent
commit
b6560da0a8
  1. 3
      aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/AbpLocalizationManagementDomainModule.cs
  2. 36
      aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LanguageProvider.cs
  3. 15
      aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationResourceDictionary.cs
  4. 95
      aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationTextCacheRefreshWorker.cs
  5. 71
      aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationTextStoreCache.cs

3
aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/AbpLocalizationManagementDomainModule.cs

@ -44,6 +44,9 @@ public class AbpLocalizationManagementDomainModule : AbpModule
options.EtoMappings.Add<Language, LanguageEto>();
options.EtoMappings.Add<Resource, ResourceEto>();
});
// 定期更新本地化缓存缓解措施
context.Services.AddHostedService<LocalizationTextCacheRefreshWorker>();
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)

36
aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LanguageProvider.cs

@ -1,36 +0,0 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Localization;
namespace LINGYUN.Abp.LocalizationManagement;
[ExposeServices(
typeof(ILanguageProvider),
typeof(LanguageProvider))]
public class LanguageProvider : ILanguageProvider, ITransientDependency
{
protected ILanguageRepository Repository { get; }
public LanguageProvider(ILanguageRepository repository)
{
Repository = repository;
}
public async virtual Task<IReadOnlyList<LanguageInfo>> GetLanguagesAsync()
{
var languages = await Repository.GetActivedListAsync();
return languages.Select(MapToLanguageInfo).ToImmutableList();
}
protected virtual LanguageInfo MapToLanguageInfo(Language language)
{
return new LanguageInfo(
language.CultureName,
language.UiCultureName,
language.DisplayName);
}
}

15
aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationResourceDictionary.cs

@ -0,0 +1,15 @@
using Microsoft.Extensions.Localization;
using System.Collections.Concurrent;
namespace LINGYUN.Abp.LocalizationManagement;
public class LocalizationResourceDictionary : ConcurrentDictionary<string, LocalizationCultureDictionary>
{
}
public class LocalizationCultureDictionary : ConcurrentDictionary<string, LocalizationTextDictionary>
{
}
public class LocalizationTextDictionary : ConcurrentDictionary<string, LocalizedString>
{
}

95
aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationTextCacheRefreshWorker.cs

@ -0,0 +1,95 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.ExceptionHandling;
using Volo.Abp.Localization;
using Volo.Abp.Localization.External;
using Volo.Abp.Threading;
namespace LINGYUN.Abp.LocalizationManagement;
/// <summary>
/// 本地化缓存刷新作业
/// </summary>
public class LocalizationTextCacheRefreshWorker : BackgroundService
{
private readonly AbpAsyncTimer _timer;
private readonly IServiceScopeFactory _serviceScopeFactory;
public ILogger<LocalizationTextCacheRefreshWorker> Logger { protected get; set; }
protected CancellationToken StoppingToken { get; set; }
public LocalizationTextCacheRefreshWorker(
AbpAsyncTimer timer,
IServiceScopeFactory serviceScopeFactory)
{
_serviceScopeFactory = serviceScopeFactory;
_timer = timer;
_timer.Period = 60000;
_timer.Elapsed += Timer_Elapsed;
Logger = NullLogger<LocalizationTextCacheRefreshWorker>.Instance;
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
StoppingToken = stoppingToken;
_timer.Start(stoppingToken);
return Task.CompletedTask;
}
private async Task Timer_Elapsed(AbpAsyncTimer timer)
{
await DoWorkAsync(StoppingToken);
}
public async virtual Task DoWorkAsync(CancellationToken cancellationToken = default)
{
using (var scope = _serviceScopeFactory.CreateScope())
{
try
{
// 定期刷新本地化缓存
var cache = scope.ServiceProvider.GetService<LocalizationTextStoreCache>();
if (cache != null)
{
var options = scope.ServiceProvider.GetRequiredService<IOptions<AbpLocalizationOptions>>().Value;
var languageProvider = scope.ServiceProvider.GetRequiredService<ILanguageProvider>();
var externalLocalizationStore = scope.ServiceProvider.GetRequiredService<IExternalLocalizationStore>();
var languages = await languageProvider.GetLanguagesAsync();
var resources = options
.Resources
.Values
.Union(
await externalLocalizationStore.GetResourcesAsync()
).ToArray();
foreach (var language in languages)
{
foreach (var resource in resources)
{
await cache.UpdateStaticCache(resource, language.CultureName);
}
}
}
}
catch (Exception ex)
{
await scope.ServiceProvider
.GetRequiredService<IExceptionNotifier>()
.NotifyAsync(new ExceptionNotificationContext(ex));
Logger.LogException(ex);
}
}
}
}

71
aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationTextStoreCache.cs

@ -1,17 +1,20 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Localization;
using Volo.Abp.Threading;
namespace LINGYUN.Abp.LocalizationManagement;
public class LocalizationTextStoreCache : ILocalizationTextStoreCache, ISingletonDependency
{
private static readonly SemaphoreSlim _cacheLock = new SemaphoreSlim(1, 1);
private static readonly LocalizationResourceDictionary _staticCache = new LocalizationResourceDictionary();
protected IServiceScopeFactory ServiceScopeFactory { get; }
protected IDistributedCache<LocalizationTextCacheItem> LocalizationTextCache { get; }
public LocalizationTextStoreCache(
@ -24,11 +27,13 @@ public class LocalizationTextStoreCache : ILocalizationTextStoreCache, ISingleto
public virtual void Fill(LocalizationResourceBase resource, string cultureName, Dictionary<string, LocalizedString> dictionary)
{
var cacheItem = GetCacheItem(resource, cultureName);
foreach (var text in cacheItem.Texts)
if (_staticCache.TryGetValue(resource.ResourceName, out var cultureLocalCache) &&
cultureLocalCache.TryGetValue(cultureName, out var textLocalCache))
{
dictionary[text.Key] = new LocalizedString(text.Key, text.Value);
foreach (var text in textLocalCache)
{
dictionary[text.Key] = new LocalizedString(text.Key, text.Value);
}
}
}
@ -38,54 +43,35 @@ public class LocalizationTextStoreCache : ILocalizationTextStoreCache, ISingleto
foreach (var text in cacheItem.Texts)
{
dictionary[text.Key] = new LocalizedString(text.Key, text.Value);
var localizedString = new LocalizedString(text.Key, text.Value);
dictionary[text.Key] = localizedString;
}
}
public virtual LocalizedString GetOrNull(LocalizationResourceBase resource, string cultureName, string name)
{
var cacheItem = GetCacheItem(resource, cultureName);
var value = cacheItem.Texts.GetOrDefault(name);
if (value.IsNullOrWhiteSpace())
if (_staticCache.TryGetValue(resource.ResourceName, out var cultureLocalCache) &&
cultureLocalCache.TryGetValue(cultureName, out var textLocalCache))
{
return null;
return textLocalCache.GetOrDefault(name);
}
return new LocalizedString(name, value);
return null;
}
protected virtual LocalizationTextCacheItem GetCacheItem(LocalizationResourceBase resource, string cultureName)
internal async Task UpdateStaticCache(LocalizationResourceBase resource, string cultureName)
{
var cacheKey = LocalizationTextCacheItem.CalculateCacheKey(resource.ResourceName, cultureName);
var cacheItem = LocalizationTextCache.Get(cacheKey);
if (cacheItem != null)
using (await _cacheLock.LockAsync())
{
return cacheItem;
}
var cacheItem = await GetCacheItemAsync(resource, cultureName);
var textDic = _staticCache
.GetOrAdd(resource.ResourceName, _ => new LocalizationCultureDictionary())
.GetOrAdd(cultureName, _ => new LocalizationTextDictionary());
var setTexts = new Dictionary<string, string>();
using (var scope = ServiceScopeFactory.CreateScope())
{
var provider = scope.ServiceProvider.GetRequiredService<IEntityChangeTrackingProvider>();
using (provider.Change(false))
foreach (var text in cacheItem.Texts)
{
var repo = scope.ServiceProvider.GetRequiredService<ITextRepository>();
#pragma warning disable CS0618
var texts = repo.GetList(resource.ResourceName, cultureName);
#pragma warning restore CS0618
foreach (var text in texts)
{
setTexts[text.Key] = text.Value;
}
}
textDic[text.Key] = new LocalizedString(text.Key, text.Value);
}
}
cacheItem = new LocalizationTextCacheItem(resource.ResourceName, cultureName, setTexts);
LocalizationTextCache.Set(cacheKey, cacheItem);
return cacheItem;
}
protected async virtual Task<LocalizationTextCacheItem> GetCacheItemAsync(LocalizationResourceBase resource, string cultureName)
@ -100,11 +86,10 @@ public class LocalizationTextStoreCache : ILocalizationTextStoreCache, ISingleto
var setTexts = new Dictionary<string, string>();
using (var scope = ServiceScopeFactory.CreateScope())
{
var provider = scope.ServiceProvider.GetRequiredService<IEntityChangeTrackingProvider>();
using (provider.Change(false))
var repo = scope.ServiceProvider.GetRequiredService<ITextRepository>();
var texts = await repo.GetListAsync(resource.ResourceName, cultureName);
using (repo.DisableTracking())
{
var repo = scope.ServiceProvider.GetRequiredService<ITextRepository>();
var texts = await repo.GetListAsync(resource.ResourceName, cultureName);
foreach (var text in texts)
{
setTexts[text.Key] = text.Value;

Loading…
Cancel
Save