From 54e9c7962d96687e439cc37d57aefb030d446fff Mon Sep 17 00:00:00 2001
From: Seany <17074267@qq.com>
Date: Sun, 14 Dec 2025 02:43:40 +0800
Subject: [PATCH] add jobs
---
.../Services/JsonStringLocalizer.cs | 82 ++++++++++
.../Services/LocalizationProvider.cs | 77 +--------
.../Services/WasmLocalizationProvider.cs | 65 +++++++-
Atomx.Admin/Atomx.Admin/Atomx.Admin.csproj | 1 +
.../Controllers/LocaleResourceController.cs | 34 ++--
.../Middlewares/RequestCultureMiddleware.cs | 6 +-
Atomx.Admin/Atomx.Admin/Program.cs | 4 +
.../Services/LocalizationFileService.cs | 147 ------------------
Atomx.Core/Jos/LocalizationJob.cs | 71 +++++----
...JobsService.cs => BackgroundJobService.cs} | 11 +-
10 files changed, 220 insertions(+), 278 deletions(-)
create mode 100644 Atomx.Admin/Atomx.Admin.Client/Services/JsonStringLocalizer.cs
delete mode 100644 Atomx.Admin/Atomx.Admin/Services/LocalizationFileService.cs
rename Atomx.Core/Services/{BackgroundJobsService.cs => BackgroundJobService.cs} (78%)
diff --git a/Atomx.Admin/Atomx.Admin.Client/Services/JsonStringLocalizer.cs b/Atomx.Admin/Atomx.Admin.Client/Services/JsonStringLocalizer.cs
new file mode 100644
index 0000000..ae91bdf
--- /dev/null
+++ b/Atomx.Admin/Atomx.Admin.Client/Services/JsonStringLocalizer.cs
@@ -0,0 +1,82 @@
+using Microsoft.Extensions.Localization;
+using System.Globalization;
+
+namespace Atomx.Admin.Client.Services
+{
+ ///
+ /// 基于 ILocalizationProvider 的 IStringLocalizer 实现:
+ /// 使用 JSON 文件中的键值,未找到返回 key 本身。
+ /// 名称改为 JsonStringLocalizer 避免与框架的 StringLocalizer 冲突。
+ ///
+ public class JsonStringLocalizer : IStringLocalizer
+ {
+ private readonly ILocalizationProvider _provider;
+
+ public JsonStringLocalizer(ILocalizationProvider provider)
+ {
+ _provider = provider;
+ }
+
+ public LocalizedString this[string name]
+ {
+ get
+ {
+ var value = _provider.GetString(name);
+ if (value == null)
+ {
+ // Avoid synchronous blocking during server prerender. Start background load and return key.
+ try
+ {
+ _ = _provider.LoadCultureAsync(_provider.CurrentCulture);
+ }
+ catch { }
+ }
+
+ var result = value ?? name;
+ return new LocalizedString(name, result, resourceNotFound: result == name);
+ }
+ }
+
+ public LocalizedString this[string name, params object[] arguments]
+ {
+ get
+ {
+ var fmt = _provider.GetString(name);
+ if (fmt == null)
+ {
+ try
+ {
+ _ = _provider.LoadCultureAsync(_provider.CurrentCulture);
+ }
+ catch { }
+ }
+
+ var format = fmt ?? name;
+ var value = string.Format(format, arguments);
+ return new LocalizedString(name, value, resourceNotFound: format == name);
+ }
+ }
+
+ public IEnumerable GetAllStrings(bool includeParentCultures)
+ {
+ var list = new List();
+ var providerType = _provider.GetType();
+ var currentProp = providerType.GetProperty("CurrentCulture");
+ var culture = currentProp?.GetValue(_provider) as string ?? string.Empty;
+ var cacheField = providerType.GetField("_cache", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
+ if (!string.IsNullOrEmpty(culture) && cacheField?.GetValue(_provider) is Dictionary> cache && cache.TryGetValue(culture, out var dict))
+ {
+ foreach (var kv in dict)
+ {
+ list.Add(new LocalizedString(kv.Key, kv.Value, resourceNotFound: false));
+ }
+ }
+ return list;
+ }
+
+ public IStringLocalizer WithCulture(CultureInfo culture)
+ {
+ return this;
+ }
+ }
+}
diff --git a/Atomx.Admin/Atomx.Admin.Client/Services/LocalizationProvider.cs b/Atomx.Admin/Atomx.Admin.Client/Services/LocalizationProvider.cs
index 060daa4..1a3701c 100644
--- a/Atomx.Admin/Atomx.Admin.Client/Services/LocalizationProvider.cs
+++ b/Atomx.Admin/Atomx.Admin.Client/Services/LocalizationProvider.cs
@@ -505,80 +505,5 @@ namespace Atomx.Admin.Client.Services
}
}
- ///
- /// ILocalizationProvider IStringLocalizer ʵ֣
- /// ʹ JSON ļеļֵδҵ key
- /// ƸΪ JsonStringLocalizer ܵ StringLocalizer ͻ
- ///
- public class JsonStringLocalizer : IStringLocalizer
- {
- private readonly ILocalizationProvider _provider;
-
- public JsonStringLocalizer(ILocalizationProvider provider)
- {
- _provider = provider;
- }
-
- public LocalizedString this[string name]
- {
- get
- {
- var value = _provider.GetString(name);
- if (value == null)
- {
- // Avoid synchronous blocking during server prerender. Start background load and return key.
- try
- {
- _ = _provider.LoadCultureAsync(_provider.CurrentCulture);
- }
- catch { }
- }
-
- var result = value ?? name;
- return new LocalizedString(name, result, resourceNotFound: result == name);
- }
- }
-
- public LocalizedString this[string name, params object[] arguments]
- {
- get
- {
- var fmt = _provider.GetString(name);
- if (fmt == null)
- {
- try
- {
- _ = _provider.LoadCultureAsync(_provider.CurrentCulture);
- }
- catch { }
- }
-
- var format = fmt ?? name;
- var value = string.Format(format, arguments);
- return new LocalizedString(name, value, resourceNotFound: format == name);
- }
- }
-
- public IEnumerable GetAllStrings(bool includeParentCultures)
- {
- var list = new List();
- var providerType = _provider.GetType();
- var currentProp = providerType.GetProperty("CurrentCulture");
- var culture = currentProp?.GetValue(_provider) as string ?? string.Empty;
- var cacheField = providerType.GetField("_cache", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
- if (!string.IsNullOrEmpty(culture) && cacheField?.GetValue(_provider) is Dictionary> cache && cache.TryGetValue(culture, out var dict))
- {
- foreach (var kv in dict)
- {
- list.Add(new LocalizedString(kv.Key, kv.Value, resourceNotFound: false));
- }
- }
- return list;
- }
-
- public IStringLocalizer WithCulture(CultureInfo culture)
- {
- return this;
- }
- }
+
}
diff --git a/Atomx.Admin/Atomx.Admin.Client/Services/WasmLocalizationProvider.cs b/Atomx.Admin/Atomx.Admin.Client/Services/WasmLocalizationProvider.cs
index ea4e113..cd45f04 100644
--- a/Atomx.Admin/Atomx.Admin.Client/Services/WasmLocalizationProvider.cs
+++ b/Atomx.Admin/Atomx.Admin.Client/Services/WasmLocalizationProvider.cs
@@ -13,6 +13,7 @@ namespace Atomx.Admin.Client.Services
private readonly HttpClient _httpClient;
private readonly ILogger _logger;
private readonly ILocalStorageService _localStorage;
+ private readonly ILocalizationService _localizationService;
private static readonly ConcurrentDictionary> _cache = new();
private readonly HashSet _loadingCultures = new();
private string _currentCulture = "zh-Hans";
@@ -21,12 +22,13 @@ namespace Atomx.Admin.Client.Services
private const string LocalizationStorageKey = "Localization_{0}";
private const string LocalizationVersionKey = "LocalizationVersion_{0}";
- public WasmLocalizationProvider(IJSRuntime jsRuntime, HttpClient httpClient, ILogger logger, ILocalStorageService localStorage)
+ public WasmLocalizationProvider(IJSRuntime jsRuntime, HttpClient httpClient, ILogger logger, ILocalStorageService localStorage, ILocalizationService localizationService)
{
_jsRuntime = jsRuntime;
_httpClient = httpClient;
_logger = logger;
_localStorage = localStorage;
+ _localizationService = localizationService;
}
public string CurrentCulture => _currentCulture;
@@ -51,14 +53,43 @@ namespace Atomx.Admin.Client.Services
if (_isInitialized) return;
await LoadCultureAsync(_currentCulture);
+
+ // ensure thread cultures and notify localization service
+ try
+ {
+ var ci = new CultureInfo(_currentCulture);
+ CultureInfo.DefaultThreadCurrentCulture = ci;
+ CultureInfo.DefaultThreadCurrentUICulture = ci;
+ _localizationService.SetLanguage(ci);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogDebug(ex, "Failed to set culture after initialize: {Culture}", _currentCulture);
+ }
+
_isInitialized = true;
LanguageChanged?.Invoke(this, _currentCulture);
}
public async Task SetCultureAsync(string cultureShortOrFull)
{
- _currentCulture = MapToFullCulture(cultureShortOrFull);
+ var full = MapToFullCulture(cultureShortOrFull);
+ _currentCulture = full;
+
await LoadCultureAsync(_currentCulture);
+
+ try
+ {
+ var ci = new CultureInfo(_currentCulture);
+ CultureInfo.DefaultThreadCurrentCulture = ci;
+ CultureInfo.DefaultThreadCurrentUICulture = ci;
+ _localizationService.SetLanguage(ci);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogDebug(ex, "Failed to set culture in SetCultureAsync: {Culture}", _currentCulture);
+ }
+
LanguageChanged?.Invoke(this, _currentCulture);
}
@@ -101,6 +132,20 @@ namespace Atomx.Admin.Client.Services
if (cachedVersion == serverVersion && cachedData != null)
{
_logger.LogInformation("Localization data for {Culture} is up-to-date.", cultureFull);
+
+ // ensure thread cultures and notify localization service when using cached data
+ try
+ {
+ var ci = new CultureInfo(cultureFull);
+ CultureInfo.DefaultThreadCurrentCulture = ci;
+ CultureInfo.DefaultThreadCurrentUICulture = ci;
+ _localizationService.SetLanguage(ci);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogDebug(ex, "Failed to set culture after loading from cache: {Culture}", cultureFull);
+ }
+
return;
}
@@ -115,6 +160,19 @@ namespace Atomx.Admin.Client.Services
await _localStorage.SetItemAsync(localDataKey, dict);
_logger.LogInformation("Loaded localization file for {Culture} from server and updated local storage.", cultureFull);
+
+ // ensure thread cultures and notify localization service after fetching
+ try
+ {
+ var ci = new CultureInfo(cultureFull);
+ CultureInfo.DefaultThreadCurrentCulture = ci;
+ CultureInfo.DefaultThreadCurrentUICulture = ci;
+ _localizationService.SetLanguage(ci);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogDebug(ex, "Failed to set culture after fetching: {Culture}", cultureFull);
+ }
}
catch (Exception ex)
{
@@ -127,6 +185,9 @@ namespace Atomx.Admin.Client.Services
_loadingCultures.Remove(cultureFull);
}
}
+
+ // Notify listeners that the culture has been loaded
+ LanguageChanged?.Invoke(this, cultureFull);
}
private string MapToFullCulture(string culture)
diff --git a/Atomx.Admin/Atomx.Admin/Atomx.Admin.csproj b/Atomx.Admin/Atomx.Admin/Atomx.Admin.csproj
index 75396aa..61efd74 100644
--- a/Atomx.Admin/Atomx.Admin/Atomx.Admin.csproj
+++ b/Atomx.Admin/Atomx.Admin/Atomx.Admin.csproj
@@ -18,6 +18,7 @@
+
diff --git a/Atomx.Admin/Atomx.Admin/Controllers/LocaleResourceController.cs b/Atomx.Admin/Atomx.Admin/Controllers/LocaleResourceController.cs
index d9765a2..e14a0eb 100644
--- a/Atomx.Admin/Atomx.Admin/Controllers/LocaleResourceController.cs
+++ b/Atomx.Admin/Atomx.Admin/Controllers/LocaleResourceController.cs
@@ -3,12 +3,16 @@ using Atomx.Admin.Client.Validators;
using Atomx.Admin.Services;
using Atomx.Common.Entities;
using Atomx.Common.Models;
+using Atomx.Core.Jos;
using Atomx.Data;
using Atomx.Data.CacheServices;
using Atomx.Data.Services;
+using Atomx.Utils.Json;
using Atomx.Utils.Models;
using MapsterMapper;
+using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Hosting;
namespace Atomx.Admin.Controllers
{
@@ -23,6 +27,8 @@ namespace Atomx.Admin.Controllers
private readonly IMapper _mapper;
private readonly JwtSetting _jwtSettings;
private readonly ICacheService _cacheService;
+ private readonly IBackgroundJobService _backgroundService;
+ private readonly IWebHostEnvironment _environment;
///
@@ -35,7 +41,7 @@ namespace Atomx.Admin.Controllers
///
///
///
- public LocaleResourceController(ILogger logger, IIdCreatorService idCreator, IIdentityService identityService, DataContext dbContext, IMapper mapper, JwtSetting jwtSettings, ICacheService cacheService)
+ public LocaleResourceController(ILogger logger, IIdCreatorService idCreator, IIdentityService identityService, DataContext dbContext, IMapper mapper, JwtSetting jwtSettings, ICacheService cacheService, IBackgroundJobService backgroundJobService, IWebHostEnvironment environment)
{
_logger = logger;
_idCreator = idCreator;
@@ -44,6 +50,8 @@ namespace Atomx.Admin.Controllers
_mapper = mapper;
_jwtSettings = jwtSettings;
_cacheService = cacheService;
+ _backgroundService = backgroundJobService;
+ _environment = environment;
}
///
@@ -198,6 +206,11 @@ namespace Atomx.Admin.Controllers
}
//异步更新对应的json文件
+ var wwwroot = _environment.WebRootPath;
+ var dic = new Dictionary();
+ dic.Add(data.Name,data.Value);
+
+ _backgroundService.UpdateLocalizationFile(wwwroot, model.Culture, dic.ToJson());
result = result.IsSuccess(true);
@@ -210,29 +223,16 @@ namespace Atomx.Admin.Controllers
///
///
[HttpGet("version/{culture}")]
+ [AllowAnonymous]
public async Task GetVersion(string culture)
{
- var result = new ApiResult();
+ var result = string.Empty;
var data = await _cacheService.GetLanguageByCulture(culture);
if (data != null)
{
- result = result.IsSuccess(data.ResourceVersion);
+ result = data.ResourceVersion;
}
return new JsonResult(result);
}
-
- ///
- /// 获取文化语言数据
- ///
- ///
- ///
- [HttpGet("resources/{culture}")]
- public IActionResult GetLocaleResources(string culture)
- {
- var result = new ApiResult();
-
-
- return new JsonResult(result);
- }
}
}
diff --git a/Atomx.Admin/Atomx.Admin/Middlewares/RequestCultureMiddleware.cs b/Atomx.Admin/Atomx.Admin/Middlewares/RequestCultureMiddleware.cs
index 127dd0e..39eba59 100644
--- a/Atomx.Admin/Atomx.Admin/Middlewares/RequestCultureMiddleware.cs
+++ b/Atomx.Admin/Atomx.Admin/Middlewares/RequestCultureMiddleware.cs
@@ -53,7 +53,7 @@ namespace Atomx.Admin.Middlewares
logger?.LogWarning(ex, "Failed to set thread culture to {Culture}", cultureName);
}
- // Attempt to synchronously load localization for server-side rendering
+ // 尝试同步加载服务器端渲染的本地化内容
try
{
var providerObj = context.RequestServices.GetService(typeof(Atomx.Admin.Client.Services.ILocalizationProvider));
@@ -61,10 +61,10 @@ namespace Atomx.Admin.Middlewares
{
logger?.LogDebug("ILocalizationProvider not registered in RequestServices");
}
- else if (providerObj is Atomx.Admin.Client.Services.LocalizationProvider provider)
+ else if (providerObj is Atomx.Admin.Services.ServerLocalizationProvider provider)
{
logger?.LogDebug("Calling SetCultureForServer on LocalizationProvider with {Culture}", cultureName);
- provider.SetCultureForServer(cultureName);
+ _= provider.SetCultureAsync(cultureName);
logger?.LogInformation("LocalizationProvider.CurrentCulture after SetCultureForServer: {Culture}", provider.CurrentCulture);
}
else
diff --git a/Atomx.Admin/Atomx.Admin/Program.cs b/Atomx.Admin/Atomx.Admin/Program.cs
index def4087..65b8a85 100644
--- a/Atomx.Admin/Atomx.Admin/Program.cs
+++ b/Atomx.Admin/Atomx.Admin/Program.cs
@@ -7,6 +7,7 @@ using Atomx.Admin.Models;
using Atomx.Admin.Services;
using Atomx.Admin.Utils;
using Atomx.Common.Models;
+using Atomx.Core.Jos;
using Atomx.Data;
using Atomx.Data.Services;
using Atomx.Utils.Json.Converts;
@@ -180,6 +181,9 @@ builder.Services.Configure(builder.Configuration.GetSection("
// ע FluentValidation ֤
builder.Services.AddValidatorsFromAssembly(typeof(Atomx.Admin.Client.Validators.LoginModelValidator).Assembly);
+// Register IBackgroundJobService and its implementation
+builder.Services.AddScoped();
+
var app = builder.Build();
app.AddDataMigrate();
diff --git a/Atomx.Admin/Atomx.Admin/Services/LocalizationFileService.cs b/Atomx.Admin/Atomx.Admin/Services/LocalizationFileService.cs
deleted file mode 100644
index 7208dae..0000000
--- a/Atomx.Admin/Atomx.Admin/Services/LocalizationFileService.cs
+++ /dev/null
@@ -1,147 +0,0 @@
-using Atomx.Common.Models;
-using System.Text.Json;
-
-namespace Atomx.Admin.Services
-{
- public interface ILocalizationFileService
- {
- Task> GetTranslationsAsync(string culture);
- Task SaveTranslationsAsync(string version, string culture, Dictionary translations);
- Task FileExistsAsync(string culture);
- }
-
- public class LocalizationFileService : ILocalizationFileService
- {
- private readonly IWebHostEnvironment _environment;
- private readonly ILogger _logger;
- private readonly string _resourcesPath;
-
- public LocalizationFileService(
- IWebHostEnvironment environment,
- ILogger logger)
- {
- _environment = environment;
- _logger = logger;
- _resourcesPath = Path.Combine(_environment.ContentRootPath, "Resources");
-
- EnsureResourcesDirectoryExists();
- }
-
- ///
- /// 读取指定文化语言的译文
- ///
- ///
- ///
- public async Task> GetTranslationsAsync(string culture)
- {
- var filePath = GetFilePath(culture);
-
- if (!File.Exists(filePath))
- {
- _logger.LogWarning("Localization file not found for culture: {Culture}", culture);
- return new Dictionary();
- }
-
- try
- {
- var json = await File.ReadAllTextAsync(filePath);
- var fileData = JsonSerializer.Deserialize(json, new JsonSerializerOptions
- {
- PropertyNamingPolicy = JsonNamingPolicy.CamelCase
- });
-
- return fileData?.Translations ?? new Dictionary();
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error reading localization file for culture: {Culture}", culture);
- return new Dictionary();
- }
- }
-
- ///
- /// 保存本地化译文
- ///
- ///
- ///
- ///
- ///
- public async Task SaveTranslationsAsync(string version, string culture, Dictionary translations)
- {
- try
- {
- var fileData = new LocalizationFile
- {
- ResourceVersion = culture,
- Translations = translations
- };
-
- var options = new JsonSerializerOptions
- {
- PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
- WriteIndented = true
- };
-
- var json = JsonSerializer.Serialize(fileData, options);
- var filePath = GetFilePath(culture);
-
- await File.WriteAllTextAsync(filePath, json);
-
- _logger.LogInformation("Saved localization file for culture: {Culture} with {Count} translations",
- culture, translations.Count);
-
- return true;
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error saving localization file for culture: {Culture}", culture);
- return false;
- }
- }
-
- ///
- /// 判断文化语言文件是否存在
- ///
- ///
- ///
- public Task FileExistsAsync(string culture)
- {
- var filePath = GetFilePath(culture);
- return Task.FromResult(File.Exists(filePath));
- }
-
- ///
- /// 获取文件最后修改时间
- ///
- ///
- ///
- public DateTime GetFileLastModified(string culture)
- {
- var filePath = GetFilePath(culture);
- return File.Exists(filePath) ? File.GetLastWriteTimeUtc(filePath) : DateTime.MinValue;
- }
-
- ///
- /// 获取文件路径
- ///
- ///
- ///
- private string GetFilePath(string culture)
- {
- var fileName = $"{culture}.json";
- return Path.Combine(_resourcesPath, fileName);
- }
-
- ///
- /// 判断存放资源文件的文件夹是否存在,不存在则创建
- ///
- private void EnsureResourcesDirectoryExists()
- {
- if (!Directory.Exists(_resourcesPath))
- {
- Directory.CreateDirectory(_resourcesPath);
- _logger.LogInformation("Created Resources directory: {Path}", _resourcesPath);
- }
- }
- }
-}
diff --git a/Atomx.Core/Jos/LocalizationJob.cs b/Atomx.Core/Jos/LocalizationJob.cs
index 44faa4e..b2a16c3 100644
--- a/Atomx.Core/Jos/LocalizationJob.cs
+++ b/Atomx.Core/Jos/LocalizationJob.cs
@@ -1,6 +1,9 @@
-using Atomx.Utils.Json;
+using Atomx.Data;
+using Atomx.Data.CacheServices;
+using Atomx.Utils.Json;
using Hangfire;
using Microsoft.Extensions.Logging;
+using System.Security.Cryptography;
using System.Text.Json;
namespace Atomx.Core.Jos
@@ -12,9 +15,13 @@ namespace Atomx.Core.Jos
public class LocalizationJob
{
readonly ILogger _logger;
- public LocalizationJob(ILogger logger)
+ readonly DataContext _dbContext;
+ readonly ICacheService _cacheService;
+ public LocalizationJob(ILogger logger, DataContext dataContext, ICacheService cacheService)
{
_logger = logger;
+ _dbContext = dataContext;
+ _cacheService = cacheService;
}
///
@@ -30,46 +37,58 @@ namespace Atomx.Core.Jos
var fileName = $"{culture}.json";
var filePath = Path.Combine(path, fileName);
- if (!Directory.Exists(filePath))
+ if (!Directory.Exists(path))
{
- Directory.CreateDirectory(filePath);
- _logger.LogInformation("Created Resources directory: {Path}", filePath);
+ Directory.CreateDirectory(path);
+ _logger.LogInformation("Created Resources directory: {Path}", path);
}
- var json = await File.ReadAllTextAsync(filePath);
- var fileData = JsonSerializer.Deserialize>(json, new JsonSerializerOptions
+ var fileData = new Dictionary();
+ if (File.Exists(filePath))
{
- PropertyNamingPolicy = JsonNamingPolicy.CamelCase
- });
- if (fileData == null)
- {
- fileData = new Dictionary();
+ var json = await File.ReadAllTextAsync(filePath);
+ fileData = JsonSerializer.Deserialize>(json, new JsonSerializerOptions
+ {
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase
+ }) ?? new Dictionary();
}
+
foreach (var item in translations)
{
- if (fileData.ContainsKey(item.Key))
- {
- fileData[item.Key] = item.Value;
- }
- else
- {
- fileData.Add(item.Key, item.Value);
- }
+ fileData[item.Key] = item.Value;
}
+
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true
};
- json = JsonSerializer.Serialize(fileData, options);
- await File.WriteAllTextAsync(filePath, json);
+ var updatedJson = JsonSerializer.Serialize(fileData, options);
+ await File.WriteAllTextAsync(filePath, updatedJson);
+
+ // 更新文件后,更新数据库中的资源版本
+ string fileHash;
+ using (var sha256 = SHA256.Create())
+ using (var stream = File.OpenRead(filePath))
+ {
+ var hashBytes = sha256.ComputeHash(stream);
+ fileHash = BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
+ }
+ var language = _dbContext.Languages.FirstOrDefault(l => l.Culture == culture);
+ if (language != null)
+ {
+ language.UpdateTime = DateTime.UtcNow;
+ language.ResourceVersion = fileHash;
+ await _dbContext.SaveChangesAsync();
+ await _cacheService.GetLanguageById(language.Id, language);
+ }
-
- _logger.LogInformation("Saved localization file for culture: {Culture} with {Count} translations",
- culture, translations.Count);
+ _logger.LogInformation("Saved localization file for culture: {Culture} with {Count} translations. File hash: {Hash}",
+ culture, translations.Count, fileHash);
}
- catch(Exception ex) {
+ catch (Exception ex)
+ {
_logger.LogError(ex, "Error saving localization file for culture: {Culture}", culture);
}
}
diff --git a/Atomx.Core/Services/BackgroundJobsService.cs b/Atomx.Core/Services/BackgroundJobService.cs
similarity index 78%
rename from Atomx.Core/Services/BackgroundJobsService.cs
rename to Atomx.Core/Services/BackgroundJobService.cs
index c3d317d..dd94c63 100644
--- a/Atomx.Core/Services/BackgroundJobsService.cs
+++ b/Atomx.Core/Services/BackgroundJobService.cs
@@ -1,12 +1,9 @@
using Hangfire;
using Microsoft.Extensions.Logging;
-using System;
-using System.Collections.Generic;
-using System.Text;
namespace Atomx.Core.Jos
{
- public partial interface IBackgroundJobsService
+ public partial interface IBackgroundJobService
{
///
/// 更新本地化文件
@@ -19,12 +16,12 @@ namespace Atomx.Core.Jos
string SendSMSVerificationCode(string phoneNumber, string code, TimeSpan validDuration);
}
- public partial class BackgroundJobsService : IBackgroundJobsService
+ public partial class BackgroundJobService : IBackgroundJobService
{
readonly IBackgroundJobClient _backgroundJobClient;
readonly IRecurringJobManager _recurringJobManager;
- readonly ILogger _logger;
- public BackgroundJobsService(IBackgroundJobClient backgroundJobClient, IRecurringJobManager recurringJobManager, ILogger logger)
+ readonly ILogger _logger;
+ public BackgroundJobService(IBackgroundJobClient backgroundJobClient, IRecurringJobManager recurringJobManager, ILogger logger)
{
_backgroundJobClient = backgroundJobClient;
_recurringJobManager = recurringJobManager;