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 { /// /// 多语言本地化任务 /// public class LocalizationJob { readonly ILogger _logger; readonly DataContext _dbContext; readonly ICacheService _cacheService; public LocalizationJob(ILogger logger, DataContext dataContext, ICacheService cacheService) { _logger = logger; _dbContext = dataContext; _cacheService = cacheService; } /// /// 如果任务失败,重试 3 次,超过后删除任务,60 秒内不允许并发执行 /// [AutomaticRetry(Attempts = 3, OnAttemptsExceeded = AttemptsExceededAction.Delete)] [DisableConcurrentExecution(60)] public async Task ExecuteAsync(string path, string culture, string data) { try { var translations = data.FromJson>(); if (translations == null) { _logger.LogError("No translations provided for culture: {Culture}", culture); } else { var fileName = $"{culture}.json"; var filePath = Path.Combine(path, fileName); if (!Directory.Exists(path)) { Directory.CreateDirectory(path); _logger.LogInformation("Created Resources directory: {Path}", path); } var fileData = new Dictionary(); if (File.Exists(filePath)) { var json = await File.ReadAllTextAsync(filePath); fileData = JsonSerializer.Deserialize>(json, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }) ?? new Dictionary(); } foreach (var item in translations) { fileData[item.Key] = item.Value; } var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = true }; 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(); Console.WriteLine(fileHash); } 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. File hash: {Hash}", culture, translations.Count, fileHash); } } catch (Exception ex) { _logger.LogError(ex, "Error saving localization file for culture: {Culture}", culture); } } /// /// 重构指定文化的本地化文件 /// /// /// [AutomaticRetry(Attempts = 3, OnAttemptsExceeded = AttemptsExceededAction.Delete)] [DisableConcurrentExecution(60)] public void RebuildCultureFile(string path, string culture) { var language = _dbContext.Languages.FirstOrDefault(l => l.Culture == culture); if (language != null) { var translations = _dbContext.LocaleResources .Where(lr => lr.LanguageId == language.Id) .ToDictionary(lr => lr.Name, lr => lr.Value); var data = JsonSerializer.Serialize(translations, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = true }); _ = ExecuteAsync(path, culture, data); } _logger.LogError("Language not found for culture: {Culture}, cannot rebuild localization file.", culture); } } }