Skip to content

Commit b77fd0c

Browse files
authored
Merge pull request #1305 from colinin/localized-static-cache
feat(localization): regularly refresh the localized static cache
2 parents 615b7c4 + 6ed0e4a commit b77fd0c

File tree

5 files changed

+151
-43
lines changed

5 files changed

+151
-43
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ public override void ConfigureServices(ServiceConfigurationContext context)
4444
options.EtoMappings.Add<Language, LanguageEto>();
4545
options.EtoMappings.Add<Resource, ResourceEto>();
4646
});
47+
48+
// 定期更新本地化缓存缓解措施
49+
context.Services.AddHostedService<LocalizationTextCacheRefreshWorker>();
4750
}
4851

4952
public override void OnApplicationInitialization(ApplicationInitializationContext context)

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

Lines changed: 0 additions & 36 deletions
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using Microsoft.Extensions.Localization;
2+
using System.Collections.Concurrent;
3+
4+
namespace LINGYUN.Abp.LocalizationManagement;
5+
public class LocalizationResourceDictionary : ConcurrentDictionary<string, LocalizationCultureDictionary>
6+
{
7+
}
8+
9+
public class LocalizationCultureDictionary : ConcurrentDictionary<string, LocalizationTextDictionary>
10+
{
11+
}
12+
13+
public class LocalizationTextDictionary : ConcurrentDictionary<string, LocalizedString>
14+
{
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
using Microsoft.Extensions.Hosting;
3+
using Microsoft.Extensions.Logging;
4+
using Microsoft.Extensions.Logging.Abstractions;
5+
using Microsoft.Extensions.Options;
6+
using System;
7+
using System.Linq;
8+
using System.Threading;
9+
using System.Threading.Tasks;
10+
using Volo.Abp.ExceptionHandling;
11+
using Volo.Abp.Localization;
12+
using Volo.Abp.Localization.External;
13+
using Volo.Abp.Threading;
14+
15+
namespace LINGYUN.Abp.LocalizationManagement;
16+
/// <summary>
17+
/// 本地化缓存刷新作业
18+
/// </summary>
19+
public class LocalizationTextCacheRefreshWorker : BackgroundService
20+
{
21+
private readonly AbpAsyncTimer _timer;
22+
private readonly IServiceScopeFactory _serviceScopeFactory;
23+
24+
public ILogger<LocalizationTextCacheRefreshWorker> Logger { protected get; set; }
25+
protected CancellationToken StoppingToken { get; set; }
26+
27+
public LocalizationTextCacheRefreshWorker(
28+
AbpAsyncTimer timer,
29+
IServiceScopeFactory serviceScopeFactory)
30+
{
31+
_serviceScopeFactory = serviceScopeFactory;
32+
_timer = timer;
33+
_timer.Period = 60000;
34+
_timer.Elapsed += Timer_Elapsed;
35+
36+
Logger = NullLogger<LocalizationTextCacheRefreshWorker>.Instance;
37+
}
38+
39+
protected override Task ExecuteAsync(CancellationToken stoppingToken)
40+
{
41+
StoppingToken = stoppingToken;
42+
_timer.Start(stoppingToken);
43+
44+
return Task.CompletedTask;
45+
}
46+
47+
private async Task Timer_Elapsed(AbpAsyncTimer timer)
48+
{
49+
await DoWorkAsync(StoppingToken);
50+
}
51+
52+
public async virtual Task DoWorkAsync(CancellationToken cancellationToken = default)
53+
{
54+
using (var scope = _serviceScopeFactory.CreateScope())
55+
{
56+
try
57+
{
58+
// 定期刷新本地化缓存
59+
var cache = scope.ServiceProvider.GetService<LocalizationTextStoreCache>();
60+
if (cache != null)
61+
{
62+
var options = scope.ServiceProvider.GetRequiredService<IOptions<AbpLocalizationOptions>>().Value;
63+
var languageProvider = scope.ServiceProvider.GetRequiredService<ILanguageProvider>();
64+
var externalLocalizationStore = scope.ServiceProvider.GetRequiredService<IExternalLocalizationStore>();
65+
66+
var languages = await languageProvider.GetLanguagesAsync();
67+
68+
var resources = options
69+
.Resources
70+
.Values
71+
.Union(
72+
await externalLocalizationStore.GetResourcesAsync()
73+
).ToArray();
74+
75+
76+
foreach (var language in languages)
77+
{
78+
foreach (var resource in resources)
79+
{
80+
await cache.UpdateStaticCache(resource, language.CultureName);
81+
}
82+
}
83+
}
84+
}
85+
catch (Exception ex)
86+
{
87+
await scope.ServiceProvider
88+
.GetRequiredService<IExceptionNotifier>()
89+
.NotifyAsync(new ExceptionNotificationContext(ex));
90+
91+
Logger.LogException(ex);
92+
}
93+
}
94+
}
95+
}

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

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
using Microsoft.Extensions.DependencyInjection;
22
using Microsoft.Extensions.Localization;
33
using System.Collections.Generic;
4+
using System.Threading;
45
using System.Threading.Tasks;
56
using Volo.Abp.Caching;
67
using Volo.Abp.DependencyInjection;
78
using Volo.Abp.Domain.Repositories;
89
using Volo.Abp.Localization;
10+
using Volo.Abp.Threading;
911

1012
namespace LINGYUN.Abp.LocalizationManagement;
1113

1214
public class LocalizationTextStoreCache : ILocalizationTextStoreCache, ISingletonDependency
1315
{
16+
private static readonly SemaphoreSlim _cacheLock = new SemaphoreSlim(1, 1);
17+
private static readonly LocalizationResourceDictionary _staticCache = new LocalizationResourceDictionary();
1418
protected IServiceScopeFactory ServiceScopeFactory { get; }
1519
protected IDistributedCache<LocalizationTextCacheItem> LocalizationTextCache { get; }
1620
public LocalizationTextStoreCache(
@@ -23,7 +27,14 @@ public LocalizationTextStoreCache(
2327

2428
public virtual void Fill(LocalizationResourceBase resource, string cultureName, Dictionary<string, LocalizedString> dictionary)
2529
{
26-
// 同步本地化函数不执行, 阻塞线程影响性能
30+
if (_staticCache.TryGetValue(resource.ResourceName, out var cultureLocalCache) &&
31+
cultureLocalCache.TryGetValue(cultureName, out var textLocalCache))
32+
{
33+
foreach (var text in textLocalCache)
34+
{
35+
dictionary[text.Key] = new LocalizedString(text.Key, text.Value);
36+
}
37+
}
2738
}
2839

2940
public async virtual Task FillAsync(LocalizationResourceBase resource, string cultureName, Dictionary<string, LocalizedString> dictionary)
@@ -32,16 +43,37 @@ public async virtual Task FillAsync(LocalizationResourceBase resource, string cu
3243

3344
foreach (var text in cacheItem.Texts)
3445
{
35-
dictionary[text.Key] = new LocalizedString(text.Key, text.Value);
46+
var localizedString = new LocalizedString(text.Key, text.Value);
47+
dictionary[text.Key] = localizedString;
3648
}
3749
}
3850

3951
public virtual LocalizedString GetOrNull(LocalizationResourceBase resource, string cultureName, string name)
4052
{
41-
// 同步本地化函数不执行, 阻塞线程影响性能
53+
if (_staticCache.TryGetValue(resource.ResourceName, out var cultureLocalCache) &&
54+
cultureLocalCache.TryGetValue(cultureName, out var textLocalCache))
55+
{
56+
return textLocalCache.GetOrDefault(name);
57+
}
4258
return null;
4359
}
4460

61+
internal async Task UpdateStaticCache(LocalizationResourceBase resource, string cultureName)
62+
{
63+
using (await _cacheLock.LockAsync())
64+
{
65+
var cacheItem = await GetCacheItemAsync(resource, cultureName);
66+
var textDic = _staticCache
67+
.GetOrAdd(resource.ResourceName, _ => new LocalizationCultureDictionary())
68+
.GetOrAdd(cultureName, _ => new LocalizationTextDictionary());
69+
70+
foreach (var text in cacheItem.Texts)
71+
{
72+
textDic[text.Key] = new LocalizedString(text.Key, text.Value);
73+
}
74+
}
75+
}
76+
4577
protected async virtual Task<LocalizationTextCacheItem> GetCacheItemAsync(LocalizationResourceBase resource, string cultureName)
4678
{
4779
// 异步本地化函数不受影响
@@ -55,11 +87,10 @@ protected async virtual Task<LocalizationTextCacheItem> GetCacheItemAsync(Locali
5587
var setTexts = new Dictionary<string, string>();
5688
using (var scope = ServiceScopeFactory.CreateScope())
5789
{
58-
var provider = scope.ServiceProvider.GetRequiredService<IEntityChangeTrackingProvider>();
59-
using (provider.Change(false))
90+
var repo = scope.ServiceProvider.GetRequiredService<ITextRepository>();
91+
var texts = await repo.GetListAsync(resource.ResourceName, cultureName);
92+
using (repo.DisableTracking())
6093
{
61-
var repo = scope.ServiceProvider.GetRequiredService<ITextRepository>();
62-
var texts = await repo.GetListAsync(resource.ResourceName, cultureName);
6394
foreach (var text in texts)
6495
{
6596
setTexts[text.Key] = text.Value;

0 commit comments

Comments
 (0)