diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/AbpLocalizationManagementDomainModule.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/AbpLocalizationManagementDomainModule.cs index 5cdc53e8e..84ca34c40 100644 --- a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/AbpLocalizationManagementDomainModule.cs +++ b/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(); options.EtoMappings.Add(); }); + + // 定期更新本地化缓存缓解措施 + context.Services.AddHostedService(); } public override void OnApplicationInitialization(ApplicationInitializationContext context) diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LanguageProvider.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LanguageProvider.cs deleted file mode 100644 index 53884261e..000000000 --- a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LanguageProvider.cs +++ /dev/null @@ -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> 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); - } -} diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationResourceDictionary.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationResourceDictionary.cs new file mode 100644 index 000000000..feeb8f85e --- /dev/null +++ b/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 +{ +} + +public class LocalizationCultureDictionary : ConcurrentDictionary +{ +} + +public class LocalizationTextDictionary : ConcurrentDictionary +{ +} diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationTextCacheRefreshWorker.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationTextCacheRefreshWorker.cs new file mode 100644 index 000000000..26c764a8e --- /dev/null +++ b/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; +/// +/// 本地化缓存刷新作业 +/// +public class LocalizationTextCacheRefreshWorker : BackgroundService +{ + private readonly AbpAsyncTimer _timer; + private readonly IServiceScopeFactory _serviceScopeFactory; + + public ILogger 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.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(); + if (cache != null) + { + var options = scope.ServiceProvider.GetRequiredService>().Value; + var languageProvider = scope.ServiceProvider.GetRequiredService(); + var externalLocalizationStore = scope.ServiceProvider.GetRequiredService(); + + 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() + .NotifyAsync(new ExceptionNotificationContext(ex)); + + Logger.LogException(ex); + } + } + } +} diff --git a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationTextStoreCache.cs b/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationTextStoreCache.cs index 851f7679e..3e0afb6bf 100644 --- a/aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationTextStoreCache.cs +++ b/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 LocalizationTextCache { get; } public LocalizationTextStoreCache( @@ -24,11 +27,13 @@ public class LocalizationTextStoreCache : ILocalizationTextStoreCache, ISingleto public virtual void Fill(LocalizationResourceBase resource, string cultureName, Dictionary 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(); - using (var scope = ServiceScopeFactory.CreateScope()) - { - var provider = scope.ServiceProvider.GetRequiredService(); - using (provider.Change(false)) + foreach (var text in cacheItem.Texts) { - var repo = scope.ServiceProvider.GetRequiredService(); -#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 GetCacheItemAsync(LocalizationResourceBase resource, string cultureName) @@ -100,11 +86,10 @@ public class LocalizationTextStoreCache : ILocalizationTextStoreCache, ISingleto var setTexts = new Dictionary(); using (var scope = ServiceScopeFactory.CreateScope()) { - var provider = scope.ServiceProvider.GetRequiredService(); - using (provider.Change(false)) + var repo = scope.ServiceProvider.GetRequiredService(); + var texts = await repo.GetListAsync(resource.ResourceName, cultureName); + using (repo.DisableTracking()) { - var repo = scope.ServiceProvider.GetRequiredService(); - var texts = await repo.GetListAsync(resource.ResourceName, cultureName); foreach (var text in texts) { setTexts[text.Key] = text.Value;