Browse Source

Merge pull request #1305 from colinin/localized-static-cache

feat(localization): regularly refresh the localized static cache
pull/1312/head
yx lin 8 months ago
committed by GitHub
parent
commit
b77fd0ccec
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  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. 45
      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<Language, LanguageEto>();
options.EtoMappings.Add<Resource, ResourceEto>(); options.EtoMappings.Add<Resource, ResourceEto>();
}); });
// 定期更新本地化缓存缓解措施
context.Services.AddHostedService<LocalizationTextCacheRefreshWorker>();
} }
public override void OnApplicationInitialization(ApplicationInitializationContext context) 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);
}
}
}
}

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

@ -1,16 +1,20 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization; using Microsoft.Extensions.Localization;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp.Caching; using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories; using Volo.Abp.Domain.Repositories;
using Volo.Abp.Localization; using Volo.Abp.Localization;
using Volo.Abp.Threading;
namespace LINGYUN.Abp.LocalizationManagement; namespace LINGYUN.Abp.LocalizationManagement;
public class LocalizationTextStoreCache : ILocalizationTextStoreCache, ISingletonDependency 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 IServiceScopeFactory ServiceScopeFactory { get; }
protected IDistributedCache<LocalizationTextCacheItem> LocalizationTextCache { get; } protected IDistributedCache<LocalizationTextCacheItem> LocalizationTextCache { get; }
public LocalizationTextStoreCache( public LocalizationTextStoreCache(
@ -23,7 +27,14 @@ public class LocalizationTextStoreCache : ILocalizationTextStoreCache, ISingleto
public virtual void Fill(LocalizationResourceBase resource, string cultureName, Dictionary<string, LocalizedString> dictionary) public virtual void Fill(LocalizationResourceBase resource, string cultureName, Dictionary<string, LocalizedString> dictionary)
{ {
// 同步本地化函数不执行, 阻塞线程影响性能 if (_staticCache.TryGetValue(resource.ResourceName, out var cultureLocalCache) &&
cultureLocalCache.TryGetValue(cultureName, out var textLocalCache))
{
foreach (var text in textLocalCache)
{
dictionary[text.Key] = new LocalizedString(text.Key, text.Value);
}
}
} }
public async virtual Task FillAsync(LocalizationResourceBase resource, string cultureName, Dictionary<string, LocalizedString> dictionary) public async virtual Task FillAsync(LocalizationResourceBase resource, string cultureName, Dictionary<string, LocalizedString> dictionary)
@ -32,16 +43,37 @@ public class LocalizationTextStoreCache : ILocalizationTextStoreCache, ISingleto
foreach (var text in cacheItem.Texts) 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) public virtual LocalizedString GetOrNull(LocalizationResourceBase resource, string cultureName, string name)
{ {
// 同步本地化函数不执行, 阻塞线程影响性能 if (_staticCache.TryGetValue(resource.ResourceName, out var cultureLocalCache) &&
cultureLocalCache.TryGetValue(cultureName, out var textLocalCache))
{
return textLocalCache.GetOrDefault(name);
}
return null; return null;
} }
internal async Task UpdateStaticCache(LocalizationResourceBase resource, string cultureName)
{
using (await _cacheLock.LockAsync())
{
var cacheItem = await GetCacheItemAsync(resource, cultureName);
var textDic = _staticCache
.GetOrAdd(resource.ResourceName, _ => new LocalizationCultureDictionary())
.GetOrAdd(cultureName, _ => new LocalizationTextDictionary());
foreach (var text in cacheItem.Texts)
{
textDic[text.Key] = new LocalizedString(text.Key, text.Value);
}
}
}
protected async virtual Task<LocalizationTextCacheItem> GetCacheItemAsync(LocalizationResourceBase resource, string cultureName) protected async virtual Task<LocalizationTextCacheItem> GetCacheItemAsync(LocalizationResourceBase resource, string cultureName)
{ {
// 异步本地化函数不受影响 // 异步本地化函数不受影响
@ -55,11 +87,10 @@ public class LocalizationTextStoreCache : ILocalizationTextStoreCache, ISingleto
var setTexts = new Dictionary<string, string>(); var setTexts = new Dictionary<string, string>();
using (var scope = ServiceScopeFactory.CreateScope()) using (var scope = ServiceScopeFactory.CreateScope())
{ {
var provider = scope.ServiceProvider.GetRequiredService<IEntityChangeTrackingProvider>(); var repo = scope.ServiceProvider.GetRequiredService<ITextRepository>();
using (provider.Change(false)) 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) foreach (var text in texts)
{ {
setTexts[text.Key] = text.Value; setTexts[text.Key] = text.Value;

Loading…
Cancel
Save