Browse Source

feat: Optimize dynamic localization initialization

pull/1501/head
colin 3 months ago
parent
commit
b49790ed88
  1. 5
      aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain.Shared/LINGYUN/Abp/LocalizationManagement/DynamicLocalizationChangedEto.cs
  2. 53
      aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain.Shared/LINGYUN/Abp/LocalizationManagement/DynamicLocalizationInitializerEto.cs
  3. 11
      aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain.Shared/LINGYUN/Abp/LocalizationManagement/ResourceChangedEto.cs
  4. 1
      aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/AbpLocalizationManagementDomainModule.cs
  5. 4
      aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/AbpLocalizationManagementOptions.cs
  6. 51
      aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/DynamicLocalizationChangedEventHandler.cs
  7. 73
      aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/DynamicLocalizationInitializerCacheItem.cs
  8. 379
      aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/DynamicLocalizationInitializerEeventHandler.cs
  9. 112
      aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/External/ExternalLocalizationStoreCache.cs
  10. 71
      aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/External/ExternalLocalizationTextStoreCache.cs
  11. 2
      aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/External/IExternalLocalizationStoreCache.cs
  12. 2
      aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/External/IExternalLocalizationTextStoreCache.cs
  13. 36
      aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/External/LocalizationResourceCacheInvalidator.cs
  14. 5
      aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/External/LocalizationResourceCacheItem.cs
  15. 3
      aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/IResourceRepository.cs
  16. 21
      aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationLanguageCacheInvalidator.cs
  17. 51
      aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationLanguageStoreCache.cs
  18. 32
      aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationTextCacheInvalidator.cs
  19. 61
      aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationTextStoreCache.cs
  20. 344
      aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/StaticLocalizationSaver.cs
  21. 7
      aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.EntityFrameworkCore/LINGYUN/Abp/LocalizationManagement/EntityFrameworkCore/EfCoreResourceRepository.cs

5
aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain.Shared/LINGYUN/Abp/LocalizationManagement/DynamicLocalizationChangedEto.cs

@ -0,0 +1,5 @@
namespace LINGYUN.Abp.LocalizationManagement;
public class DynamicLocalizationChangedEto
{
}

53
aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain.Shared/LINGYUN/Abp/LocalizationManagement/DynamicLocalizationInitializerEto.cs

@ -0,0 +1,53 @@
namespace LINGYUN.Abp.LocalizationManagement;
public abstract class DynamicLocalizationInitializerEto
{
public string[] Keys { get; set; }
protected DynamicLocalizationInitializerEto()
{
}
protected DynamicLocalizationInitializerEto(string[] keys)
{
Keys = keys;
}
}
public class DynamicLanguageInitializerEto : DynamicLocalizationInitializerEto
{
public DynamicLanguageInitializerEto()
{
}
public DynamicLanguageInitializerEto(string[] keys) : base(keys)
{
}
}
public class DynamicResourceInitializerEto : DynamicLocalizationInitializerEto
{
public DynamicResourceInitializerEto()
{
}
public DynamicResourceInitializerEto(string[] keys) : base(keys)
{
}
}
public class DynamicTextInitializerEto : DynamicLocalizationInitializerEto
{
public DynamicTextInitializerEto()
{
}
public DynamicTextInitializerEto(string[] keys) : base(keys)
{
}
}

11
aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain.Shared/LINGYUN/Abp/LocalizationManagement/ResourceChangedEto.cs

@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
namespace LINGYUN.Abp.LocalizationManagement;
[Serializable]
public class ResourceChangedEto
{
public List<string> Resources { get; set; }
public List<string> Cultures { get; set; }
}

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

@ -20,6 +20,7 @@ namespace LINGYUN.Abp.LocalizationManagement;
public class AbpLocalizationManagementDomainModule : AbpModule
{
private readonly CancellationTokenSource _cancellationTokenSource = new();
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddMapperlyObjectMapper<AbpLocalizationManagementDomainModule>();

4
aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/AbpLocalizationManagementOptions.cs

@ -6,6 +6,10 @@ public class AbpLocalizationManagementOptions
/// 保存本地化文本到数据库
/// </summary>
public bool SaveStaticLocalizationsToDatabase { get; set; } = true;
/// <summary>
/// 是否动态本地化文本初始化节点
/// </summary>
public bool IsDynamicLocalizationInitializerHost { get; set; } = false;
public AbpLocalizationManagementOptions()
{

51
aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/DynamicLocalizationChangedEventHandler.cs

@ -0,0 +1,51 @@
using LINGYUN.Abp.LocalizationManagement.External;
using System.Threading.Tasks;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Distributed;
namespace LINGYUN.Abp.LocalizationManagement;
public class DynamicLocalizationChangedEventHandler :
IDistributedEventHandler<DynamicLocalizationChangedEto>,
ITransientDependency
{
protected IDistributedCache<LocalizationResourcesCacheItem> ResourcesCache { get; }
protected IDistributedCache<LocalizationLanguageCacheItem> LanguageCache { get; }
protected IExternalLocalizationTextStoreCache ExternalLocalizationTextStoreCache { get; }
protected ILocalizationTextStoreCache LocalizationTextStoreCache { get; }
public DynamicLocalizationChangedEventHandler(
IDistributedCache<LocalizationResourcesCacheItem> resourcesCache,
IDistributedCache<LocalizationLanguageCacheItem> languageCache,
IExternalLocalizationTextStoreCache externalLocalizationTextStoreCache,
ILocalizationTextStoreCache localizationTextStoreCache)
{
ResourcesCache = resourcesCache;
LanguageCache = languageCache;
ExternalLocalizationTextStoreCache = externalLocalizationTextStoreCache;
LocalizationTextStoreCache = localizationTextStoreCache;
}
public async virtual Task HandleEventAsync(DynamicLocalizationChangedEto eventData)
{
var resourcesCacheItem = await ResourcesCache.GetAsync(LocalizationResourcesCacheItem.CacheKey);
var languagesCacheItem = await LanguageCache.GetAsync(LocalizationLanguageCacheItem.CacheKey);
foreach (var language in languagesCacheItem.Languages)
{
foreach (var resource in resourcesCacheItem.Resources)
{
if (ExternalLocalizationTextStoreCache is ExternalLocalizationTextStoreCache storeCache)
{
await storeCache.UpdateCache(resource.Name, language.CultureName);
}
if (LocalizationTextStoreCache is LocalizationTextStoreCache textStoreCache)
{
await textStoreCache.UpdateCache(resource.Name, language.CultureName);
}
}
}
}
}

73
aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/DynamicLocalizationInitializerCacheItem.cs

@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using Volo.Abp.Caching;
using Volo.Abp.MultiTenancy;
namespace LINGYUN.Abp.LocalizationManagement;
[Serializable]
[IgnoreMultiTenancy]
[CacheName("DynamicLanguageInitializer")]
public class DynamicLanguageInitializerCacheItem
{
public string CultureName { get; set; }
public string UiCultureName { get; set; }
public string DisplayName { get; set; }
public string TwoLetterISOLanguageName { get; set; }
public DynamicLanguageInitializerCacheItem()
{
}
public DynamicLanguageInitializerCacheItem(
string cultureName,
string uiCultureName,
string displayName,
string twoLetterISOLanguageName)
{
CultureName = cultureName;
UiCultureName = uiCultureName;
DisplayName = displayName;
TwoLetterISOLanguageName = twoLetterISOLanguageName;
}
}
[Serializable]
[IgnoreMultiTenancy]
[CacheName("DynamicResourceInitializer")]
public class DynamicResourceInitializerCacheItem
{
public string ResourceName { get; set; }
public string DefaultCultureName { get; set; }
public DynamicResourceInitializerCacheItem()
{
}
public DynamicResourceInitializerCacheItem(
string resourceName,
string defaultCultureName)
{
ResourceName = resourceName;
DefaultCultureName = defaultCultureName;
}
}
[Serializable]
[IgnoreMultiTenancy]
[CacheName("DynamicTextInitializer")]
public class DynamicTextInitializerCacheItem
{
public string ResourceName { get; set; }
public string CultureName { get; set; }
public Dictionary<string, string> Texts { get; set; }
public DynamicTextInitializerCacheItem()
{
Texts = new Dictionary<string, string>();
}
public DynamicTextInitializerCacheItem(
string resourceName,
string cultureName,
Dictionary<string, string> texts)
{
ResourceName = resourceName;
CultureName = cultureName;
Texts = texts;
}
}

379
aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/DynamicLocalizationInitializerEeventHandler.cs

@ -0,0 +1,379 @@
using LINGYUN.Abp.LocalizationManagement.External;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.DistributedLocking;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Guids;
using Volo.Abp.Localization;
using Volo.Abp.Uow;
namespace LINGYUN.Abp.LocalizationManagement;
public class DynamicLocalizationInitializerEeventHandler :
IDistributedEventHandler<DynamicLanguageInitializerEto>,
IDistributedEventHandler<DynamicResourceInitializerEto>,
IDistributedEventHandler<DynamicTextInitializerEto>,
ITransientDependency
{
public ILogger<StaticLocalizationSaver> Logger { protected get; set; }
protected IDistributedCache<LocalizationResourceCacheItem> ResourceCache { get; }
protected IDistributedCache<LocalizationResourcesCacheItem> ResourcesCache { get; }
protected IDistributedCache<LocalizationLanguageCacheItem> LanguageCache { get; }
protected IDistributedCache<LocalizationTextCacheItem> LocalizationTextCache { get; }
protected IDistributedCache<DynamicLanguageInitializerCacheItem> DynamicLanguageCache { get; }
protected IDistributedCache<DynamicResourceInitializerCacheItem> DynamicResourceCache { get; }
protected IDistributedCache<DynamicTextInitializerCacheItem> DynamicTextCache { get; }
protected AbpLocalizationManagementOptions LocalizationManagementOptions { get; }
protected IGuidGenerator GuidGenerator { get; }
protected IAbpDistributedLock DistributedLock { get; }
protected IDistributedEventBus DistributedEventBus { get; }
protected IUnitOfWorkManager UnitOfWorkManager { get; }
protected ILanguageRepository LanguageRepository { get; }
protected IResourceRepository ResourceRepository { get; }
protected ITextRepository TextRepository { get; }
public DynamicLocalizationInitializerEeventHandler(
IDistributedCache<LocalizationResourceCacheItem> resourceCache,
IDistributedCache<LocalizationResourcesCacheItem> resourcesCache,
IDistributedCache<LocalizationLanguageCacheItem> languageCache,
IDistributedCache<LocalizationTextCacheItem> localizationTextCache,
IDistributedCache<DynamicLanguageInitializerCacheItem> dynamicLanguageCache,
IDistributedCache<DynamicResourceInitializerCacheItem> dynamicResourceCache,
IDistributedCache<DynamicTextInitializerCacheItem> dynamicTextCache,
IOptions<AbpLocalizationManagementOptions> localizationManagementOptions,
IGuidGenerator guidGenerator,
IAbpDistributedLock distributedLock,
IDistributedEventBus distributedEventBus,
IUnitOfWorkManager unitOfWorkManager,
ILanguageRepository languageRepository,
IResourceRepository resourceRepository,
ITextRepository textRepository)
{
ResourceCache = resourceCache;
ResourcesCache = resourcesCache;
LanguageCache = languageCache;
LocalizationTextCache = localizationTextCache;
DynamicLanguageCache = dynamicLanguageCache;
DynamicResourceCache = dynamicResourceCache;
DynamicTextCache = dynamicTextCache;
LocalizationManagementOptions = localizationManagementOptions.Value;
GuidGenerator = guidGenerator;
DistributedLock = distributedLock;
DistributedEventBus = distributedEventBus;
UnitOfWorkManager = unitOfWorkManager;
LanguageRepository = languageRepository;
ResourceRepository = resourceRepository;
TextRepository = textRepository;
Logger = NullLogger<StaticLocalizationSaver>.Instance;
}
public async virtual Task HandleEventAsync(DynamicLanguageInitializerEto eventData)
{
if (!LocalizationManagementOptions.SaveStaticLocalizationsToDatabase ||
!LocalizationManagementOptions.IsDynamicLocalizationInitializerHost)
{
return;
}
Logger.LogDebug("Waiting to acquire the distributed lock for saving static languages...");
await using var applicationLockHandle = await DistributedLock.TryAcquireAsync(
$"{nameof(DynamicLocalizationInitializerEeventHandler)}_{nameof(DynamicLanguageInitializerEto)}",
TimeSpan.FromSeconds(5));
if (applicationLockHandle == null)
{
throw new AbpException("Failed to acquire the initialization lock for the localization languages!");
}
using var unitOfWork = UnitOfWorkManager.Begin(true, true);
var cacheItems = await DynamicLanguageCache.GetManyAsync(eventData.Keys);
await SaveLanguagesAsync(cacheItems.Select(kv => kv.Value).ToArray());
Logger.LogInformation("Refresh language cache items.");
await RefreshLanguagesCacheAsync();
await DynamicLanguageCache.RemoveManyAsync(eventData.Keys);
await DistributedEventBus.PublishAsync(new DynamicLocalizationChangedEto());
await unitOfWork.CompleteAsync();
Logger.LogInformation("Completed to save static languages.");
}
public async virtual Task HandleEventAsync(DynamicResourceInitializerEto eventData)
{
if (!LocalizationManagementOptions.SaveStaticLocalizationsToDatabase ||
!LocalizationManagementOptions.IsDynamicLocalizationInitializerHost)
{
return;
}
Logger.LogDebug("Waiting to acquire the distributed lock for saving static resources...");
await using var applicationLockHandle = await DistributedLock.TryAcquireAsync(
$"{nameof(DynamicLocalizationInitializerEeventHandler)}_{nameof(DynamicResourceInitializerEto)}",
TimeSpan.FromSeconds(5));
if (applicationLockHandle == null)
{
throw new AbpException("Failed to acquire the initialization lock for the localization resources!");
}
using var unitOfWork = UnitOfWorkManager.Begin(true, true);
var cacheItems = await DynamicResourceCache.GetManyAsync(eventData.Keys);
await SaveResourcesAsync(cacheItems.Select(kv => kv.Value).ToArray());
Logger.LogInformation("Refresh resource cache items.");
await RefreshResourcesCacheAsync();
await DynamicResourceCache.RemoveManyAsync(eventData.Keys);
await DistributedEventBus.PublishAsync(new DynamicLocalizationChangedEto());
await unitOfWork.CompleteAsync();
Logger.LogInformation("Completed to save static resources.");
}
public async virtual Task HandleEventAsync(DynamicTextInitializerEto eventData)
{
if (!LocalizationManagementOptions.SaveStaticLocalizationsToDatabase ||
!LocalizationManagementOptions.IsDynamicLocalizationInitializerHost)
{
return;
}
Logger.LogDebug("Waiting to acquire the distributed lock for saving static texts...");
await using var applicationLockHandle = await DistributedLock.TryAcquireAsync(
$"{nameof(DynamicLocalizationInitializerEeventHandler)}_{nameof(DynamicTextInitializerEto)}",
TimeSpan.FromSeconds(5));
if (applicationLockHandle == null)
{
throw new AbpException("Failed to acquire the initialization lock for the localization texts!");
}
using var unitOfWork = UnitOfWorkManager.Begin(true, true);
var cacheItemDics = await DynamicTextCache.GetManyAsync(eventData.Keys);
var cacheItems = cacheItemDics.Select(kv => kv.Value).ToArray();
await SaveTextsAsync(cacheItems);
Logger.LogInformation("Refresh text cache items.");
await RefreshTextsCacheAsync(cacheItems);
await DynamicResourceCache.RemoveManyAsync(eventData.Keys);
await DistributedEventBus.PublishAsync(new DynamicLocalizationChangedEto());
await unitOfWork.CompleteAsync();
Logger.LogInformation("Completed to save static texts.");
}
private async Task SaveLanguagesAsync(DynamicLanguageInitializerCacheItem[] cacheItems)
{
var newLanguages = new List<Language>();
var updateLanguages = new List<Language>();
var existsLanguages = await LanguageRepository.GetListAsync();
foreach (var cacheItem in cacheItems)
{
var existsLanguage = existsLanguages.FirstOrDefault(x => x.CultureName == cacheItem.CultureName);
if (existsLanguage == null)
{
newLanguages.Add(
new Language(
GuidGenerator.Create(),
cacheItem.CultureName,
cacheItem.UiCultureName,
cacheItem.DisplayName,
cacheItem.TwoLetterISOLanguageName));
}
else
{
var isUpdated = false;
if (existsLanguage.DisplayName != cacheItem.DisplayName)
{
isUpdated = true;
existsLanguage.SetDisplayName(cacheItem.DisplayName);
}
if (existsLanguage.TwoLetterISOLanguageName != cacheItem.TwoLetterISOLanguageName)
{
isUpdated = true;
existsLanguage.SetTwoLetterISOLanguageName(cacheItem.TwoLetterISOLanguageName);
}
if (isUpdated)
{
updateLanguages.Add(existsLanguage);
}
}
}
if (newLanguages.Count > 0)
{
Logger.LogInformation("Saved {0} new languages.", newLanguages.Count);
await LanguageRepository.InsertManyAsync(newLanguages, autoSave: true);
}
if (updateLanguages.Count > 0)
{
Logger.LogInformation("Update {0} changed languages.", updateLanguages.Count);
await LanguageRepository.UpdateManyAsync(updateLanguages, autoSave: true);
}
}
private async Task RefreshLanguagesCacheAsync()
{
var languages = await LanguageRepository.GetListAsync();
var languageCacheItem = new LocalizationLanguageCacheItem(
languages.Select(x =>
new LanguageInfo(x.CultureName, x.UiCultureName, x.DisplayName))
.ToList());
await LanguageCache.SetAsync(LocalizationLanguageCacheItem.CacheKey, languageCacheItem);
}
private async Task SaveResourcesAsync(DynamicResourceInitializerCacheItem[] cacheItems)
{
var newResources = new List<Resource>();
var updateResources = new List<Resource>();
var existsResources = await ResourceRepository.GetListAsync();
foreach (var cacheItem in cacheItems)
{
var existsResource = existsResources.FirstOrDefault(x => x.Name == cacheItem.ResourceName);
if (existsResource == null)
{
newResources.Add(
new Resource(
GuidGenerator.Create(),
cacheItem.ResourceName,
cacheItem.ResourceName,
cacheItem.ResourceName,
cacheItem.DefaultCultureName));
}
else if (existsResource.DefaultCultureName != cacheItem.DefaultCultureName)
{
existsResource.SetDefaultCultureName(cacheItem.DefaultCultureName);
updateResources.Add(existsResource);
}
}
if (newResources.Count > 0)
{
Logger.LogInformation("Saved {0} new resources.", newResources.Count);
await ResourceRepository.InsertManyAsync(newResources, autoSave: true);
}
if (updateResources.Count > 0)
{
Logger.LogInformation("Update {0} changed resources.", updateResources.Count);
await ResourceRepository.UpdateManyAsync(updateResources, autoSave: true);
}
}
private async Task RefreshResourcesCacheAsync()
{
var resources = await ResourceRepository.GetListAsync();
var allResourceCacheItems = new List<LocalizationResourceCacheItem>();
var allResourceCacheItemDics = new Dictionary<string, LocalizationResourceCacheItem>();
foreach (var resource in resources)
{
var resourceCacheItem = new LocalizationResourceCacheItem(resource.Name, resource.DefaultCultureName)
{
IsEnabled = resource.Enable
};
allResourceCacheItems.Add(resourceCacheItem);
allResourceCacheItemDics[resource.Name] = resourceCacheItem;
}
await ResourcesCache.SetAsync(
LocalizationResourcesCacheItem.CacheKey,
new LocalizationResourcesCacheItem(allResourceCacheItems));
await ResourceCache.SetManyAsync(allResourceCacheItemDics);
}
private async Task SaveTextsAsync(DynamicTextInitializerCacheItem[] cacheItems)
{
var newTexts = new List<Text>();
var updateTexts = new List<Text>();
foreach (var cacheItem in cacheItems)
{
var existsTexts = await TextRepository.GetListAsync(
cacheItem.ResourceName,
cacheItem.CultureName);
foreach (var text in cacheItem.Texts)
{
var existsText = existsTexts.FirstOrDefault(x => x.Key == text.Key);
if (existsText == null)
{
newTexts.Add(
new Text(
cacheItem.ResourceName,
cacheItem.CultureName,
text.Key,
text.Value));
}
else if (existsText.Value != text.Value)
{
existsText.SetValue(text.Value);
updateTexts.Add(existsText);
}
}
}
if (newTexts.Count > 0)
{
Logger.LogInformation("Saved {0} new texts.", newTexts.Count);
await TextRepository.InsertManyAsync(newTexts, autoSave: true);
}
if (updateTexts.Count > 0)
{
Logger.LogInformation("Update {0} changed texts.", updateTexts.Count);
await TextRepository.UpdateManyAsync(updateTexts, autoSave: true);
}
}
private async Task RefreshTextsCacheAsync(DynamicTextInitializerCacheItem[] cacheItems)
{
foreach (var cacheItem in cacheItems)
{
var setTexts = new Dictionary<string, string>();
var allTexts = await TextRepository.GetListAsync(cacheItem.ResourceName, cacheItem.CultureName);
foreach (var text in allTexts)
{
setTexts[text.Key] = text.Value;
}
var textCacheKey = LocalizationTextCacheItem.CalculateCacheKey(cacheItem.ResourceName, cacheItem.CultureName);
var textCacheItem = new LocalizationTextCacheItem(cacheItem.ResourceName, cacheItem.CultureName, setTexts);
await LocalizationTextCache.SetAsync(textCacheKey, textCacheItem);
}
}
}

112
aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/External/ExternalLocalizationStoreCache.cs

@ -1,10 +1,8 @@
using Microsoft.Extensions.Options;
using System;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.DistributedLocking;
using Volo.Abp.Localization;
namespace LINGYUN.Abp.LocalizationManagement.External;
@ -14,32 +12,20 @@ public class ExternalLocalizationStoreCache : IExternalLocalizationStoreCache, I
protected IDistributedCache<LocalizationResourceCacheItem> ResourceCache { get; }
protected IDistributedCache<LocalizationResourcesCacheItem> ResourcesCache { get; }
protected AbpLocalizationOptions LocalizationOptions { get; }
protected IResourceRepository ResourceRepository { get; }
protected IAbpDistributedLock DistributedLock { get; }
public ExternalLocalizationStoreCache(
IDistributedCache<LocalizationResourcesCacheItem> resourcesCache,
IDistributedCache<LocalizationResourceCacheItem> resourceCache,
IOptions<AbpLocalizationOptions> localizationOptions,
IResourceRepository resourceRepository,
IAbpDistributedLock distributedLock)
IOptions<AbpLocalizationOptions> localizationOptions)
{
ResourceCache = resourceCache;
ResourcesCache = resourcesCache;
ResourceRepository = resourceRepository;
DistributedLock = distributedLock;
LocalizationOptions = localizationOptions.Value;
}
public async virtual Task RemoveAsync(string resourceName)
{
await ResourceCache.RemoveAsync(resourceName);
await ResourcesCache.RemoveAsync(LocalizationResourcesCacheItem.CacheKey);
}
public async virtual Task<string[]> GetResourceNamesAsync()
{
var cacheItem = await GetResourcesCacheItemAsync();
var cacheItem = await ResourcesCache.GetAsync(LocalizationResourcesCacheItem.CacheKey);
return cacheItem.Resources
.Where(x => x.IsEnabled)
@ -57,108 +43,18 @@ public class ExternalLocalizationStoreCache : IExternalLocalizationStoreCache, I
public async virtual Task<LocalizationResourceCacheItem> GetResourceOrNullAsync(string resourceName)
{
var cacheItem = await GetResourceCacheItemAsync(resourceName);
var cacheItem = await ResourceCache.GetAsync(resourceName);
return cacheItem?.IsEnabled == true ? cacheItem : null;
}
public async virtual Task<LocalizationResourceCacheItem[]> GetResourcesAsync()
{
var cacheItem = await GetResourcesCacheItemAsync();
var cacheItem = await ResourcesCache.GetAsync(LocalizationResourcesCacheItem.CacheKey);
return cacheItem?.Resources
.Where(x => x.IsEnabled)
.Where(x => !LocalizationOptions.Resources.ContainsKey(x.Name))
.ToArray();
}
internal async Task UpdateCache(string resourceName)
{
if (resourceName.IsNullOrWhiteSpace())
{
await CreateResourcesCacheItemAsync();
}
else
{
await CreateResourceCacheItemAsync(resourceName);
}
}
protected async virtual Task<LocalizationResourceCacheItem> GetResourceCacheItemAsync(string resourceName)
{
var cacheItem = await ResourceCache.GetAsync(resourceName);
if (cacheItem != null)
{
return cacheItem;
}
return await CreateResourceCacheItemAsync(resourceName);
}
protected async virtual Task<LocalizationResourceCacheItem> CreateResourceCacheItemAsync(string resourceName)
{
await using var handle = await DistributedLock.TryAcquireAsync($"{nameof(ExternalLocalizationStoreCache)}_{nameof(CreateResourceCacheItemAsync)}");
if (handle == null)
{
return null;
}
LocalizationResourceCacheItem cacheItem;
var resource = await ResourceRepository.FindByNameAsync(resourceName);
if (resource == null)
{
cacheItem = new LocalizationResourceCacheItem(resourceName)
{
IsEnabled = false
};
}
else
{
cacheItem = new LocalizationResourceCacheItem(resource.Name, resource.DefaultCultureName)
{
IsEnabled = resource.Enable
};
}
await ResourceCache.SetAsync(resourceName, cacheItem);
return cacheItem;
}
protected async virtual Task<LocalizationResourcesCacheItem> GetResourcesCacheItemAsync()
{
var cacheItem = await ResourcesCache.GetAsync(LocalizationResourcesCacheItem.CacheKey);
if (cacheItem != null)
{
return cacheItem;
}
return await CreateResourcesCacheItemAsync();
}
protected async virtual Task<LocalizationResourcesCacheItem> CreateResourcesCacheItemAsync()
{
await using var handle = await DistributedLock.TryAcquireAsync($"{nameof(ExternalLocalizationStoreCache)}_{nameof(CreateResourcesCacheItemAsync)}");
if (handle == null)
{
return null;
}
var resources = await ResourceRepository.GetListAsync();
var resourceItems = resources
.Where(x => !LocalizationOptions.Resources.ContainsKey(x.Name))
.Select(x => new LocalizationResourceCacheItem(x.Name, x.DefaultCultureName)
{
IsEnabled = x.Enable,
})
.ToList();
var cacheItem = new LocalizationResourcesCacheItem(resourceItems);
await ResourcesCache.SetAsync(LocalizationResourcesCacheItem.CacheKey, cacheItem);
return cacheItem;
}
}

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

@ -1,11 +1,9 @@
using Microsoft.Extensions.DependencyInjection;
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.DistributedLocking;
using Volo.Abp.Localization;
using Volo.Abp.Threading;
@ -14,43 +12,19 @@ namespace LINGYUN.Abp.LocalizationManagement.External;
public class ExternalLocalizationTextStoreCache : IExternalLocalizationTextStoreCache, ISingletonDependency
{
protected ConcurrentDictionary<string, LocalizationTextMemoryCacheItem> MemoryCache { get; }
protected IAbpDistributedLock DistributedLock { get; }
protected IServiceScopeFactory ServiceScopeFactory { get; }
protected IDistributedCache<ExternalLocalizationTextCacheItem> DistributedCache { get; }
protected IDistributedCache<ExternalLocalizationTextStampCacheItem> StampCache { get; }
public ExternalLocalizationTextStoreCache(
IAbpDistributedLock distributedLock,
IServiceScopeFactory serviceScopeFactory,
IDistributedCache<ExternalLocalizationTextCacheItem> distributedCache,
IDistributedCache<ExternalLocalizationTextStampCacheItem> stampCache)
{
DistributedLock = distributedLock;
ServiceScopeFactory = serviceScopeFactory;
DistributedCache = distributedCache;
StampCache = stampCache;
MemoryCache = new ConcurrentDictionary<string, LocalizationTextMemoryCacheItem>();
}
public async virtual Task RemoveAsync(string resourceName, string cultureName)
{
var cacheKey = ExternalLocalizationTextCacheItem.CalculateCacheKey(resourceName, cultureName);
var stampCacheKey = ExternalLocalizationTextStampCacheItem.CalculateCacheKey(resourceName, cultureName);
await using var lockHandle = await DistributedLock.TryAcquireAsync(cacheKey);
if (lockHandle == null)
{
return;
}
await DistributedCache.RemoveAsync(cacheKey);
await StampCache.SetAsync(stampCacheKey, new ExternalLocalizationTextStampCacheItem(Guid.NewGuid().ToString()));
}
public Dictionary<string, string> GetTexts(LocalizationResourceBase resource, string cultureName)
{
var cacheKey = ExternalLocalizationTextCacheItem.CalculateCacheKey(resource.ResourceName, cultureName);
@ -97,7 +71,7 @@ public class ExternalLocalizationTextStoreCache : IExternalLocalizationTextStore
return distributeCacheItem.Texts;
}
distributeCacheItem = await CreateCacheItemAsync(resource, cultureName);
distributeCacheItem = await GetAndRefreshMemoryCacheItemAsync(resource.ResourceName, cultureName);
if (distributeCacheItem == null)
{
@ -114,41 +88,30 @@ public class ExternalLocalizationTextStoreCache : IExternalLocalizationTextStore
internal async Task UpdateCache(LocalizationResourceBase resource, string cultureName)
{
await CreateCacheItemAsync(resource, cultureName);
await GetAndRefreshMemoryCacheItemAsync(resource.ResourceName, cultureName);
}
protected async virtual Task<ExternalLocalizationTextCacheItem> CreateCacheItemAsync(LocalizationResourceBase resource, string cultureName)
internal async Task UpdateCache(string resourceName, string cultureName)
{
await using var lockHandle = await DistributedLock.TryAcquireAsync($"{nameof(ExternalLocalizationTextStoreCache)}_{nameof(CreateCacheItemAsync)}");
await GetAndRefreshMemoryCacheItemAsync(resourceName, cultureName);
}
if (lockHandle == null)
protected async virtual Task<ExternalLocalizationTextCacheItem> GetAndRefreshMemoryCacheItemAsync(string resourceName, string cultureName)
{
var cacheKey = ExternalLocalizationTextCacheItem.CalculateCacheKey(resourceName, cultureName);
var cacheItem = await DistributedCache.GetAsync(cacheKey);
if (cacheItem != null)
{
return null;
}
var cacheKey = ExternalLocalizationTextCacheItem.CalculateCacheKey(resource.ResourceName, cultureName);
var stampCacheKey = ExternalLocalizationTextStampCacheItem.CalculateCacheKey(resource.ResourceName, cultureName);
var stampCacheKey = ExternalLocalizationTextStampCacheItem.CalculateCacheKey(resourceName, cultureName);
using var scope = ServiceScopeFactory.CreateScope();
var repo = scope.ServiceProvider.GetRequiredService<ITextRepository>();
var stampCacheItem = new ExternalLocalizationTextStampCacheItem(Guid.NewGuid().ToString());
await StampCache.SetAsync(stampCacheKey, stampCacheItem);
var texts = await repo.GetListAsync(resource.ResourceName, cultureName);
MemoryCache[cacheKey] = new LocalizationTextMemoryCacheItem(cacheItem.Texts, stampCacheItem.Stamp);
var fillTexts = new Dictionary<string, string>();
foreach (var text in texts)
{
fillTexts[text.Key] = text.Value;
return cacheItem;
}
var cacheItem = new ExternalLocalizationTextCacheItem(fillTexts);
await DistributedCache.SetAsync(cacheKey, cacheItem);
var stampCacheItem = new ExternalLocalizationTextStampCacheItem(Guid.NewGuid().ToString());
await StampCache.SetAsync(stampCacheKey, stampCacheItem);
MemoryCache[cacheKey] = new LocalizationTextMemoryCacheItem(cacheItem.Texts, stampCacheItem.Stamp);
return cacheItem;
return null;
}
}

2
aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/External/IExternalLocalizationStoreCache.cs

@ -11,6 +11,4 @@ public interface IExternalLocalizationStoreCache
Task<string[]> GetResourceNamesAsync();
Task<LocalizationResourceCacheItem[]> GetResourcesAsync();
Task RemoveAsync(string resourceName);
}

2
aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/External/IExternalLocalizationTextStoreCache.cs

@ -9,6 +9,4 @@ public interface IExternalLocalizationTextStoreCache
Dictionary<string, string> GetTexts(LocalizationResourceBase resource, string cultureName);
Task<Dictionary<string, string>> GetTextsAsync(LocalizationResourceBase resource, string cultureName);
Task RemoveAsync(string resourceName, string cultureName);
}

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

@ -1,36 +0,0 @@
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events;
using Volo.Abp.EventBus;
namespace LINGYUN.Abp.LocalizationManagement.External;
public class LocalizationResourceCacheInvalidator :
ILocalEventHandler<EntityChangedEventData<Resource>>,
ITransientDependency
{
private readonly IExternalLocalizationTextStoreCache _externalLocalizationTextStoreCache;
private readonly ILocalizationLanguageStoreCache _localizationLanguageStoreCache;
private readonly IExternalLocalizationStoreCache _externalLocalizationStoreCache;
public LocalizationResourceCacheInvalidator(
IExternalLocalizationTextStoreCache externalLocalizationTextStoreCache,
ILocalizationLanguageStoreCache localizationLanguageStoreCache,
IExternalLocalizationStoreCache externalLocalizationStoreCache)
{
_externalLocalizationTextStoreCache = externalLocalizationTextStoreCache;
_localizationLanguageStoreCache = localizationLanguageStoreCache;
_externalLocalizationStoreCache = externalLocalizationStoreCache;
}
public async virtual Task HandleEventAsync(EntityChangedEventData<Resource> eventData)
{
await _externalLocalizationStoreCache.RemoveAsync(eventData.Entity.Name);
;
var languages = await _localizationLanguageStoreCache.GetLanguagesAsync();
foreach (var language in languages)
{
await _externalLocalizationTextStoreCache.RemoveAsync(eventData.Entity.Name, language.CultureName);
}
}
}

5
aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/External/LocalizationResourceCacheItem.cs

@ -18,6 +18,11 @@ public class LocalizationResourceCacheItem
public bool IsEnabled { get; set; }
public LocalizationResourceCacheItem()
{
}
public LocalizationResourceCacheItem(
string name,
string defaultCulture = null,

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

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories;
@ -16,4 +17,6 @@ public interface IResourceRepository : IRepository<Resource, Guid>
Task<Resource> FindByNameAsync(
string name,
CancellationToken cancellationToken = default);
Task<List<Resource>> GetActivedListAsync(CancellationToken cancellationToken = default);
}

21
aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationLanguageCacheInvalidator.cs

@ -1,21 +0,0 @@
using System.Threading.Tasks;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events;
using Volo.Abp.EventBus;
namespace LINGYUN.Abp.LocalizationManagement;
public class LocalizationLanguageCacheInvalidator: ILocalEventHandler<EntityChangedEventData<Language>>, ITransientDependency
{
private readonly IDistributedCache<LocalizationLanguageCacheItem> _localizationLanguageCache;
public LocalizationLanguageCacheInvalidator(IDistributedCache<LocalizationLanguageCacheItem> localizationLanguageCache)
{
_localizationLanguageCache = localizationLanguageCache;
}
public async virtual Task HandleEventAsync(EntityChangedEventData<Language> eventData)
{
await _localizationLanguageCache.RemoveAsync(LocalizationLanguageCacheItem.CacheKey);
}
}

51
aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationLanguageStoreCache.cs

@ -1,12 +1,9 @@
using Microsoft.Extensions.Options;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.DistributedLocking;
using Volo.Abp.Domain.ChangeTracking;
using Volo.Abp.Localization;
namespace LINGYUN.Abp.LocalizationManagement;
@ -14,64 +11,20 @@ namespace LINGYUN.Abp.LocalizationManagement;
public class LocalizationLanguageStoreCache : ILocalizationLanguageStoreCache, ITransientDependency
{
protected IDistributedCache<LocalizationLanguageCacheItem> LanguageCache { get; }
protected ILanguageRepository LanguageRepository { get; }
protected IAbpDistributedLock DistributedLock { get; }
protected AbpLocalizationOptions Options { get; }
public LocalizationLanguageStoreCache(
IDistributedCache<LocalizationLanguageCacheItem> languageCache,
ILanguageRepository languageRepository,
IAbpDistributedLock distributedLock,
IDistributedCache<LocalizationLanguageCacheItem> languageCache,
IOptions<AbpLocalizationOptions> options)
{
LanguageCache = languageCache;
LanguageRepository = languageRepository;
DistributedLock = distributedLock;
Options = options.Value;
}
[DisableEntityChangeTracking]
public async virtual Task<IReadOnlyList<LanguageInfo>> GetLanguagesAsync()
{
var cacheItem = await GetCacheItemAsync();
return cacheItem?.Languages.ToImmutableList() ?? [..Options.Languages];
}
internal async Task UpdateCache()
{
await CreateCacheItemAsync();
}
protected async virtual Task<LocalizationLanguageCacheItem> GetCacheItemAsync()
{
var cacheItem = await LanguageCache.GetAsync(LocalizationLanguageCacheItem.CacheKey);
if (cacheItem != null)
{
return cacheItem;
}
return await CreateCacheItemAsync();
}
protected async virtual Task<LocalizationLanguageCacheItem> CreateCacheItemAsync()
{
await using var handle = await DistributedLock.TryAcquireAsync($"{nameof(LocalizationLanguageStoreCache)}_{nameof(GetCacheItemAsync)}");
if (handle == null)
{
return null;
}
var languages = await LanguageRepository.GetListAsync();
var cacheItem = new LocalizationLanguageCacheItem(
languages.Select(x =>
new LanguageInfo(x.CultureName, x.UiCultureName, x.DisplayName))
.ToList());
await LanguageCache.SetAsync(LocalizationLanguageCacheItem.CacheKey, cacheItem);
return cacheItem;
return cacheItem?.Languages.ToImmutableList() ?? [..Options.Languages];
}
}

32
aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/LocalizationTextCacheInvalidator.cs

@ -1,32 +0,0 @@
using LINGYUN.Abp.LocalizationManagement.External;
using System.Threading.Tasks;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events;
using Volo.Abp.EventBus;
namespace LINGYUN.Abp.LocalizationManagement;
public class LocalizationTextCacheInvalidator : ILocalEventHandler<EntityChangedEventData<Text>>, ITransientDependency
{
private readonly IDistributedCache<LocalizationTextCacheItem> _localizationTextCache;
private readonly IExternalLocalizationTextStoreCache _externalLocalizationTextStoreCache;
public LocalizationTextCacheInvalidator(
IDistributedCache<LocalizationTextCacheItem> localizationTextCache,
IExternalLocalizationTextStoreCache externalLocalizationTextStoreCache)
{
_externalLocalizationTextStoreCache = externalLocalizationTextStoreCache;
_localizationTextCache = localizationTextCache;
}
public async virtual Task HandleEventAsync(EntityChangedEventData<Text> eventData)
{
var cacheKey = LocalizationTextCacheItem.CalculateCacheKey(
eventData.Entity.ResourceName,
eventData.Entity.CultureName);
await _localizationTextCache.RemoveAsync(cacheKey);
await _externalLocalizationTextStoreCache.RemoveAsync(eventData.Entity.ResourceName, eventData.Entity.CultureName);
}
}

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

@ -1,12 +1,9 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Localization;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.DistributedLocking;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Localization;
using Volo.Abp.Threading;
@ -16,17 +13,10 @@ public class LocalizationTextStoreCache : ILocalizationTextStoreCache, ISingleto
{
private static readonly SemaphoreSlim _cacheLock = new SemaphoreSlim(1, 1);
private static readonly LocalizationResourceDictionary _staticCache = new LocalizationResourceDictionary();
protected IAbpDistributedLock DistributedLock { get; }
protected IServiceScopeFactory ServiceScopeFactory { get; }
protected IDistributedCache<LocalizationTextCacheItem> LocalizationTextCache { get; }
public LocalizationTextStoreCache(
IAbpDistributedLock distributedLock,
IServiceScopeFactory serviceScopeFactory,
IDistributedCache<LocalizationTextCacheItem> localizationTextCache)
{
DistributedLock = distributedLock;
ServiceScopeFactory = serviceScopeFactory;
LocalizationTextCache = localizationTextCache;
}
@ -44,7 +34,7 @@ public class LocalizationTextStoreCache : ILocalizationTextStoreCache, ISingleto
public async virtual Task FillAsync(LocalizationResourceBase resource, string cultureName, Dictionary<string, LocalizedString> dictionary)
{
var cacheItem = await GetCacheItemAsync(resource, cultureName);
var cacheItem = await GetCacheItemAsync(resource.ResourceName, cultureName);
if (cacheItem != null)
{
foreach (var text in cacheItem.Texts)
@ -66,14 +56,19 @@ public class LocalizationTextStoreCache : ILocalizationTextStoreCache, ISingleto
}
internal async Task UpdateCache(LocalizationResourceBase resource, string cultureName)
{
await UpdateCache(resource.ResourceName, cultureName);
}
internal async Task UpdateCache(string resourceName, string cultureName)
{
using (await _cacheLock.LockAsync())
{
var cacheItem = await GetCacheItemAsync(resource, cultureName);
var cacheItem = await GetCacheItemAsync(resourceName, cultureName);
if (cacheItem != null)
{
var textDic = _staticCache
.GetOrAdd(resource.ResourceName, _ => new LocalizationCultureDictionary())
.GetOrAdd(resourceName, _ => new LocalizationCultureDictionary())
.GetOrAdd(cultureName, _ => new LocalizationTextDictionary());
foreach (var text in cacheItem.Texts)
@ -84,41 +79,9 @@ public class LocalizationTextStoreCache : ILocalizationTextStoreCache, ISingleto
}
}
protected async virtual Task<LocalizationTextCacheItem> GetCacheItemAsync(LocalizationResourceBase resource, string cultureName)
protected async virtual Task<LocalizationTextCacheItem> GetCacheItemAsync(string resourceName, string cultureName)
{
// 异步本地化函数不受影响
var cacheKey = LocalizationTextCacheItem.CalculateCacheKey(resource.ResourceName, cultureName);
var cacheItem = await LocalizationTextCache.GetAsync(cacheKey);
if (cacheItem != null)
{
return cacheItem;
}
await using var handle = await DistributedLock.TryAcquireAsync($"{nameof(LocalizationTextStoreCache)}_{nameof(GetCacheItemAsync)}");
if (handle == null)
{
return null;
}
var setTexts = new Dictionary<string, string>();
using (var scope = ServiceScopeFactory.CreateScope())
{
var repo = scope.ServiceProvider.GetRequiredService<ITextRepository>();
var texts = await repo.GetListAsync(resource.ResourceName, cultureName);
using (repo.DisableTracking())
{
foreach (var text in texts)
{
setTexts[text.Key] = text.Value;
}
}
}
cacheItem = new LocalizationTextCacheItem(resource.ResourceName, cultureName, setTexts);
await LocalizationTextCache.SetAsync(cacheKey, cacheItem);
return cacheItem;
var cacheKey = LocalizationTextCacheItem.CalculateCacheKey(resourceName, cultureName);
return await LocalizationTextCache.GetAsync(cacheKey);
}
}

344
aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/StaticLocalizationSaver.cs

@ -1,87 +1,46 @@
using LINGYUN.Abp.LocalizationManagement.External;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.DistributedLocking;
using Volo.Abp.Guids;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Localization;
using Volo.Abp.Threading;
using Volo.Abp.Uow;
namespace LINGYUN.Abp.LocalizationManagement;
public class StaticLocalizationSaver : IStaticLocalizationSaver, ITransientDependency
{
protected ILogger<StaticLocalizationSaver> Logger { get; }
protected IDistributedCache Cache { get; }
protected IGuidGenerator GuidGenerator { get; }
protected IAbpDistributedLock DistributedLock { get; }
protected IUnitOfWorkManager UnitOfWorkManager { get; }
protected AbpDistributedCacheOptions CacheOptions { get; }
protected IApplicationInfoAccessor ApplicationInfoAccessor { get; }
protected ICancellationTokenProvider CancellationTokenProvider { get; }
protected IDistributedEventBus DistributedEventBus { get; }
protected IStringLocalizerFactory StringLocalizerFactory { get; }
protected AbpLocalizationOptions LocalizationOptions { get; }
protected AbpLocalizationManagementOptions LocalizationManagementOptions { get; }
protected IExternalLocalizationTextStoreCache ExternalLocalizationTextStoreCache { get; }
protected IExternalLocalizationStoreCache ExternalLocalizationStoreCache { get; }
protected ILocalizationLanguageStoreCache LocalizationLanguageStoreCache { get; }
protected ILocalizationTextStoreCache LocalizationTextStoreCache { get; }
protected ILanguageRepository LanguageRepository { get; }
protected IResourceRepository ResourceRepository { get; }
protected ITextRepository TextRepository { get; }
protected IDistributedCache<DynamicLanguageInitializerCacheItem> DynamicLanguageCache { get; }
protected IDistributedCache<DynamicResourceInitializerCacheItem> DynamicResourceCache { get; }
protected IDistributedCache<DynamicTextInitializerCacheItem> DynamicTextCache { get; }
public StaticLocalizationSaver(
ILogger<StaticLocalizationSaver> logger,
IDistributedCache cache,
IGuidGenerator guidGenerator,
IAbpDistributedLock distributedLock,
IUnitOfWorkManager unitOfWorkManager,
IApplicationInfoAccessor applicationInfoAccessor,
ICancellationTokenProvider cancellationTokenProvider,
IStringLocalizerFactory stringLocalizerFactory,
IOptions<AbpDistributedCacheOptions> cacheOptions,
IOptions<AbpLocalizationOptions> localizationOptions,
IOptions<AbpLocalizationManagementOptions> localizationManagementOptions,
IExternalLocalizationTextStoreCache externalLocalizationTextStoreCache,
IExternalLocalizationStoreCache externalLocalizationStoreCache,
ILocalizationLanguageStoreCache localizationLanguageStoreCache,
ILocalizationTextStoreCache localizationTextStoreCache,
ILanguageRepository languageRepository,
IResourceRepository resourceRepository,
ITextRepository textRepository)
IDistributedEventBus distributedEventBus,
IStringLocalizerFactory stringLocalizerFactory,
IOptions<AbpLocalizationOptions> localizationOptions,
IOptions<AbpLocalizationManagementOptions> localizationManagementOptions,
IDistributedCache<DynamicLanguageInitializerCacheItem> dynamicLanguageCache,
IDistributedCache<DynamicResourceInitializerCacheItem> dynamicResourceCache,
IDistributedCache<DynamicTextInitializerCacheItem> dynamicTextCache)
{
Logger = logger;
Cache = cache;
GuidGenerator = guidGenerator;
DistributedLock = distributedLock;
UnitOfWorkManager = unitOfWorkManager;
CacheOptions = cacheOptions.Value;
ApplicationInfoAccessor = applicationInfoAccessor;
CancellationTokenProvider = cancellationTokenProvider;
DistributedEventBus = distributedEventBus;
StringLocalizerFactory = stringLocalizerFactory;
LocalizationOptions = localizationOptions.Value;
LocalizationManagementOptions = localizationManagementOptions.Value;
ExternalLocalizationTextStoreCache = externalLocalizationTextStoreCache;
ExternalLocalizationStoreCache = externalLocalizationStoreCache;
LocalizationLanguageStoreCache = localizationLanguageStoreCache;
LocalizationTextStoreCache = localizationTextStoreCache;
LanguageRepository = languageRepository;
ResourceRepository = resourceRepository;
TextRepository = textRepository;
DynamicLanguageCache = dynamicLanguageCache;
DynamicResourceCache = dynamicResourceCache;
DynamicTextCache = dynamicTextCache;
}
public async virtual Task SaveAsync()
@ -91,265 +50,72 @@ public class StaticLocalizationSaver : IStaticLocalizationSaver, ITransientDepen
return;
}
Logger.LogDebug("Waiting to acquire the distributed lock for saving static localizations...");
await using var applicationLockHandle = await DistributedLock.TryAcquireAsync(
GetApplicationDistributedLockKey(),
TimeSpan.FromSeconds(5));
if (applicationLockHandle == null)
{
return;
}
using var unitOfWork = UnitOfWorkManager.Begin(true, true);
try
{
await SaveLanguagesAsync();
await SaveResourcesAsync();
await SaveTextsAsync();
await PreCacheDynamicLocalizationsAsync();
}
catch (Exception ex)
{
Logger.LogInformation("Filed to save static localizations.");
Logger.LogWarning(ex.ToString());
try
{
await unitOfWork.RollbackAsync();
}
catch
{
Logger.LogInformation("Filed to rollback saving static localizations.");
}
throw;
}
await unitOfWork.CompleteAsync();
Logger.LogInformation("Completed to save static localizations.");
}
var languageCacheItems = new Dictionary<string, DynamicLanguageInitializerCacheItem>();
var resourceCacheItems = new Dictionary<string, DynamicResourceInitializerCacheItem>();
var textCacheItems = new Dictionary<string, DynamicTextInitializerCacheItem>();
private async Task PreCacheDynamicLocalizationsAsync()
{
if (LocalizationLanguageStoreCache is LocalizationLanguageStoreCache languageStoreCache)
{
await languageStoreCache.UpdateCache();
}
foreach (var resource in LocalizationOptions.Resources)
var localizationResources = LocalizationOptions.Resources.Values.OfType<LocalizationResource>().ToArray();
foreach (var language in LocalizationOptions.Languages)
{
if (ExternalLocalizationStoreCache is ExternalLocalizationStoreCache localizationStoreCache)
{
await localizationStoreCache.UpdateCache(resource.Key);
}
foreach (var language in LocalizationOptions.Languages)
if (!languageCacheItems.Any(x => x.Value.CultureName == language.CultureName))
{
if (ExternalLocalizationTextStoreCache is ExternalLocalizationTextStoreCache externalLocalizationTextStoreCache)
{
await externalLocalizationTextStoreCache.UpdateCache(resource.Value, language.CultureName);
}
if (LocalizationTextStoreCache is LocalizationTextStoreCache localizationTextStoreCache)
{
await localizationTextStoreCache.UpdateCache(resource.Value, language.CultureName);
}
languageCacheItems.Add(
Guid.NewGuid().ToString(),
new DynamicLanguageInitializerCacheItem(
language.CultureName,
language.UiCultureName,
language.DisplayName,
language.TwoLetterISOLanguageName));
}
}
}
private async Task SaveLanguagesAsync()
{
var languageHashKey = GetApplicationLanguageHashKey();
var languageHashCache = await Cache.GetStringAsync(languageHashKey, CancellationTokenProvider.Token);
var languageHash = CalculateLanguagesHash(LocalizationOptions.Languages);
if (languageHashCache != languageHash)
{
var newLanguages = new List<Language>();
foreach (var language in LocalizationOptions.Languages)
foreach (var resource in localizationResources)
{
if (await LanguageRepository.FindByCultureNameAsync(language.CultureName, cancellationToken: CancellationTokenProvider.Token) == null)
if (!resourceCacheItems.Any(x => x.Value.ResourceName == resource.ResourceName))
{
newLanguages.Add(
new Language(
GuidGenerator.Create(),
language.CultureName,
language.UiCultureName,
language.DisplayName,
language.TwoLetterISOLanguageName));
resourceCacheItems.Add(
Guid.NewGuid().ToString(),
new DynamicResourceInitializerCacheItem(
resource.ResourceName,
resource.DefaultCultureName));
}
}
if (newLanguages.Any())
{
Logger.LogInformation("Saved {0} new languages.", newLanguages.Count);
await LanguageRepository.InsertManyAsync(newLanguages, cancellationToken: CancellationTokenProvider.Token);
}
await Cache.SetStringAsync(languageHashKey, languageHash, CancellationTokenProvider.Token);
}
}
private async Task SaveResourcesAsync()
{
var resourceHashKey = GetApplicationResourceHashKey();
var resourceHashCache = await Cache.GetStringAsync(resourceHashKey, CancellationTokenProvider.Token);
var resourceHash = CalculateResourceHash(LocalizationOptions.Resources.Select(x => x.Value).ToList());
if (resourceHashCache != resourceHash)
{
var newResources = new List<Resource>();
foreach (var resource in LocalizationOptions.Resources)
{
if (await ResourceRepository.FindByNameAsync(resource.Key, cancellationToken: CancellationTokenProvider.Token) == null)
{
newResources.Add(
new Resource(
GuidGenerator.Create(),
resource.Value.ResourceName,
resource.Value.ResourceName,
resource.Value.ResourceName,
resource.Value.DefaultCultureName));
}
}
if (newResources.Any())
{
Logger.LogInformation("Saved {0} new resources.", newResources.Count);
await ResourceRepository.InsertManyAsync(newResources, cancellationToken: CancellationTokenProvider.Token);
}
await Cache.SetStringAsync(resourceHashKey, resourceHash, CancellationTokenProvider.Token);
}
}
private async Task SaveTextsAsync()
{
var createTexts = new List<Text>();
var updateTexts = new List<Text>();
var localizationResources = LocalizationOptions.Resources.Values.OfType<LocalizationResource>().ToArray();
var languageResourceTexts = new Dictionary<string, Dictionary<string, Dictionary<string, LocalizedString>>>();
foreach (var language in LocalizationOptions.Languages)
{
var resourceTexts = new Dictionary<string, Dictionary<string, LocalizedString>>();
foreach (var resource in localizationResources)
{
using (CultureHelper.Use(language.CultureName, language.UiCultureName))
{
var stringLocalizer = StringLocalizerFactory.Create(resource.ResourceType);
var localizedStrings = stringLocalizer.GetAllStrings(false, false, false);
var textHashKey = GetApplicationTextHashKey(resource.ResourceName, language.CultureName);
var textHashCache = await Cache.GetStringAsync(textHashKey, CancellationTokenProvider.Token);
var textHash = CalculateTextsHash(localizedStrings);
if (textHashCache == textHash)
if (!localizedStrings.Any())
{
continue;
}
var savedLocalizedStrings = await TextRepository.GetListAsync(
resource.ResourceName,
language.CultureName);
var localizedStringsDic = new Dictionary<string, string>();
foreach (var localizedString in localizedStrings)
{
var findLocalizedString = savedLocalizedStrings.FirstOrDefault(x => x.Key == localizedString.Name);
if (findLocalizedString == null)
{
createTexts.Add(
new Text(
resource.ResourceName,
language.CultureName,
localizedString.Name,
localizedString.Value));
continue;
}
if (!string.Equals(findLocalizedString.Value, localizedString.Value, StringComparison.InvariantCultureIgnoreCase))
{
findLocalizedString.SetValue(localizedString.Value);
updateTexts.Add(findLocalizedString);
}
localizedStringsDic.TryAdd(localizedString.Name, localizedString.Value);
}
await Cache.SetStringAsync(textHashKey, textHash, CancellationTokenProvider.Token);
textCacheItems.Add(
Guid.NewGuid().ToString(),
new DynamicTextInitializerCacheItem(
resource.ResourceName,
language.CultureName,
localizedStringsDic));
}
}
languageResourceTexts[language.CultureName] = resourceTexts;
}
if (createTexts.Any())
{
Logger.LogInformation("Saved {0} new texts.", createTexts.Count);
await TextRepository.InsertManyAsync(createTexts, cancellationToken: CancellationTokenProvider.Token);
}
if (updateTexts.Any())
{
Logger.LogInformation("Update {0} changed texts.", updateTexts.Count);
await TextRepository.UpdateManyAsync(updateTexts, cancellationToken: CancellationTokenProvider.Token);
}
}
private string GetApplicationDistributedLockKey()
{
return $"{CacheOptions.KeyPrefix}_{ApplicationInfoAccessor.ApplicationName}_AbpLocalizationsUpdateLock";
}
private string GetApplicationResourceHashKey()
{
return $"{CacheOptions.KeyPrefix}_{ApplicationInfoAccessor.ApplicationName}_AbpLocalizationResourcesHash";
}
private string GetApplicationLanguageHashKey()
{
return $"{CacheOptions.KeyPrefix}_{ApplicationInfoAccessor.ApplicationName}_AbpLocalizationLanguagesHash";
}
private string GetApplicationTextHashKey(string resourceName, string cultureName)
{
return $"{CacheOptions.KeyPrefix}_{ApplicationInfoAccessor.ApplicationName}_{resourceName}_{cultureName}_AbpLocalizationTextsHash";
}
private static string CalculateResourceHash(List<LocalizationResourceBase> localizationResources)
{
var stringBuilder = new StringBuilder();
stringBuilder.Append("LocalizationResources:");
stringBuilder.AppendLine(JsonSerializer.Serialize(localizationResources));
return stringBuilder
.ToString()
.ToMd5();
}
private static string CalculateLanguagesHash(List<LanguageInfo> languageInfos)
{
var stringBuilder = new StringBuilder();
stringBuilder.Append("LocalizationLanguages:");
stringBuilder.AppendLine(JsonSerializer.Serialize(languageInfos));
return stringBuilder
.ToString()
.ToMd5();
}
private static string CalculateTextsHash(IEnumerable<LocalizedString> localizers)
{
var stringBuilder = new StringBuilder();
stringBuilder.Append("LocalizationTexts:");
stringBuilder.AppendLine(JsonSerializer.Serialize(
localizers.Select(x => new NameValue(x.Name, x.Value)).ToList()));
await DynamicLanguageCache.SetManyAsync(languageCacheItems);
await DynamicResourceCache.SetManyAsync(resourceCacheItems);
await DynamicTextCache.SetManyAsync(textCacheItems);
return stringBuilder
.ToString()
.ToMd5();
await DistributedEventBus.PublishAsync(
new DynamicLanguageInitializerEto(languageCacheItems.Keys.ToArray()));
await DistributedEventBus.PublishAsync(
new DynamicResourceInitializerEto(resourceCacheItems.Keys.ToArray()));
await DistributedEventBus.PublishAsync(
new DynamicTextInitializerEto(textCacheItems.Keys.ToArray()));
}
}

7
aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.EntityFrameworkCore/LINGYUN/Abp/LocalizationManagement/EntityFrameworkCore/EfCoreResourceRepository.cs

@ -1,5 +1,6 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@ -40,4 +41,10 @@ public class EfCoreResourceRepository : EfCoreRepository<ILocalizationDbContext,
return await (await GetDbSetAsync()).Where(x => x.Name.Equals(name))
.FirstOrDefaultAsync(GetCancellationToken(cancellationToken));
}
public async virtual Task<List<Resource>> GetActivedListAsync(CancellationToken cancellationToken = default)
{
return await (await GetDbSetAsync()).Where(x => x.Enable)
.ToListAsync(GetCancellationToken(cancellationToken));
}
}

Loading…
Cancel
Save