From 9edff983d85f6f55e89ea62fe165f75a7c70a743 Mon Sep 17 00:00:00 2001 From: yxw <17074267@qq.com> Date: Sun, 14 Dec 2025 18:27:21 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E8=AF=AD=E8=A8=80=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E7=9A=84=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Atomx.Admin/Atomx.Admin.Client/Program.cs | 2 +- .../Services/JsonStringLocalizer.cs | 2 +- .../Services/LocalizationProvider.cs | 187 ++++++++------ .../Services/WasmLocalizationProvider.cs | 239 ------------------ .../Controllers/LocaleResourceController.cs | 59 ++++- .../Extensions/DbMigrateExtension.cs | 1 - .../Middlewares/RequestCultureMiddleware.cs | 2 +- Atomx.Admin/Atomx.Admin/Program.cs | 2 +- .../Services/ServerLocalizationProvider.cs | 86 ------- Atomx.Common/Atomx.Common.csproj | 10 +- Atomx.Common/Entities/Address.cs | 4 +- Atomx.Common/Entities/Area.cs | 2 +- Atomx.Common/Entities/Category.cs | 10 +- Atomx.Common/Entities/Language.cs | 4 +- Atomx.Common/Entities/LocaleResource.cs | 2 +- Atomx.Common/Entities/LocalizedProperty.cs | 2 +- Atomx.Common/Entities/Manufacturer.cs | 2 +- Atomx.Common/Entities/MaterialRecord.cs | 2 +- Atomx.Common/Entities/Menu.cs | 4 +- Atomx.Common/Entities/Order.cs | 8 +- Atomx.Common/Entities/OrderItem.cs | 2 +- Atomx.Common/Entities/PaymentChannel.cs | 2 +- Atomx.Common/Entities/Permission.cs | 2 +- Atomx.Common/Entities/Product.cs | 6 +- .../Entities/ProductAttributeCombination.cs | 2 +- .../Entities/ProductAttributeValue.cs | 2 +- Atomx.Common/Entities/ProductInventoryLog.cs | 2 +- Atomx.Common/Entities/ProductListing.cs | 2 +- Atomx.Common/Entities/Role.cs | 2 +- Atomx.Common/Entities/UserMeta.cs | 2 +- Atomx.Common/Entities/WarehouseStockRecord.cs | 2 +- Atomx.Core/Jos/LocalizationJob.cs | 128 ++++++---- Atomx.Core/Services/BackgroundJobService.cs | 22 ++ ...gner.cs => 20251214094758_0.1.Designer.cs} | 50 ++-- ...203190956_0.1.cs => 20251214094758_0.1.cs} | 46 ++-- .../Migrations/DataContextModelSnapshot.cs | 48 ++-- 36 files changed, 382 insertions(+), 568 deletions(-) delete mode 100644 Atomx.Admin/Atomx.Admin.Client/Services/WasmLocalizationProvider.cs delete mode 100644 Atomx.Admin/Atomx.Admin/Services/ServerLocalizationProvider.cs rename Atomx.Data/Migrations/{20251203190956_0.1.Designer.cs => 20251214094758_0.1.Designer.cs} (97%) rename Atomx.Data/Migrations/{20251203190956_0.1.cs => 20251214094758_0.1.cs} (98%) diff --git a/Atomx.Admin/Atomx.Admin.Client/Program.cs b/Atomx.Admin/Atomx.Admin.Client/Program.cs index a805b28..bd2d48d 100644 --- a/Atomx.Admin/Atomx.Admin.Client/Program.cs +++ b/Atomx.Admin/Atomx.Admin.Client/Program.cs @@ -29,7 +29,7 @@ builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder. // 注册 LocalizationProvider (用于 WASM) // Use Scoped lifetime because LocalizationProvider depends on IJSRuntime (scoped) and IHttpClient etc. -builder.Services.AddScoped(); +builder.Services.AddScoped(); // 注册 ILocalizationService 用于同步 Culture 在组件间传播 builder.Services.AddScoped(); diff --git a/Atomx.Admin/Atomx.Admin.Client/Services/JsonStringLocalizer.cs b/Atomx.Admin/Atomx.Admin.Client/Services/JsonStringLocalizer.cs index ae91bdf..bcac055 100644 --- a/Atomx.Admin/Atomx.Admin.Client/Services/JsonStringLocalizer.cs +++ b/Atomx.Admin/Atomx.Admin.Client/Services/JsonStringLocalizer.cs @@ -24,7 +24,7 @@ namespace Atomx.Admin.Client.Services var value = _provider.GetString(name); if (value == null) { - // Avoid synchronous blocking during server prerender. Start background load and return key. + // 閬垮厤鍦ㄦ湇鍔$ prerender 闃舵杩涜鍚屾闃诲銆備互鍚庡彴鏂瑰紡鍚姩鍔犺浇骞惰繑鍥 key銆 try { _ = _provider.LoadCultureAsync(_provider.CurrentCulture); diff --git a/Atomx.Admin/Atomx.Admin.Client/Services/LocalizationProvider.cs b/Atomx.Admin/Atomx.Admin.Client/Services/LocalizationProvider.cs index 1a3701c..24cfa21 100644 --- a/Atomx.Admin/Atomx.Admin.Client/Services/LocalizationProvider.cs +++ b/Atomx.Admin/Atomx.Admin.Client/Services/LocalizationProvider.cs @@ -1,16 +1,20 @@ +using Microsoft.JSInterop; using System.Collections.Concurrent; using System.Globalization; using System.Text.Json; -using Microsoft.JSInterop; -using Microsoft.Extensions.Localization; namespace Atomx.Admin.Client.Services { /// - /// 提供多语言 JSON 文件加载、缓存与运行时切换的实现。 - /// - 在 Server 环境:尝试通过反射访问 IWebHostEnvironment 的 webroot,从文件系统读取 {culture}.json 文件。 - /// - 在 WASM 环境:使用注入的 HttpClient 从 /localization/{culture}.json 下载并解析。 - /// 同时在切换语言时写入 Cookie 并设置页面 HTML lang 属性。 + /// 本类提供多语言 JSON 文件的加载、缓存与运行时切换功能。 + /// 主要职责: + /// - 在 Server 环境(例如 prerender 或 Blazor Server)时尝试从 webroot 同步读取本地化 JSON 文件并缓存, + /// 以便在服务端渲染阶段立即可用。 + /// - 在 WASM 环境时使用注入的 HttpClient 从 /localization/{culture}.json 下载并解析。 + /// - 在切换语言时写入名为 `atomx.culture` 的 cookie(供后端读取)并尝试设置页面的 HTML lang 属性。 + /// - 提供事件通知 LanguageChanged,供 UI 或其他服务订阅以响应语言变更。 + /// + /// 说明:为了兼容 Server 与 WASM,本 Provider 会尽量根据运行时环境选择最合适的资源加载方式。 /// public interface ILocalizationProvider { @@ -25,6 +29,12 @@ namespace Atomx.Admin.Client.Services event EventHandler? LanguageChanged; } + /// + /// LocalizationProvider 的实现: + /// - 维护一个静态缓存,避免重复下载/读取资源。 + /// - 支持短码(如 zh / en)与完整 culture(如 zh-Hans / en-US)之间的映射。 + /// - 在 Server 端支持同步加载以满足 prerender 场景。 + /// public class LocalizationProvider : ILocalizationProvider { private readonly IServiceProvider _sp; @@ -33,22 +43,26 @@ namespace Atomx.Admin.Client.Services private readonly ILogger _logger; private readonly ILocalizationService _localizationService; - // 缓存:culture -> translations - // Use a static concurrent dictionary so files loaded during middleware/server prerender - // are visible to provider instances created later in the same request pipeline. + // 缓存:culture -> translations。使用 ConcurrentDictionary 以线程安全地共享。 + // 使用静态字段是为了使中间件/服务在同一进程内能够复用已加载的翻译,避免重复 I/O。 private static readonly ConcurrentDictionary> _cache = new(); - // 短码到完整 culture 的映射 + // 支持的短码映射,后续如需扩展可在此添加。 private static readonly Dictionary ShortToCulture = new(StringComparer.OrdinalIgnoreCase) { { "zh", "zh-Hans" }, { "en", "en-US" } }; + // 默认文化(当未能从浏览器/URL/Cookie 中解析到文化时使用) private string _currentCulture = "zh-Hans"; private const string CookieName = "atomx.culture"; + /// + /// 构造函数:通过依赖注入获取必要服务。 + /// 注意构造函数中不应执行耗时或 JS 相关的同步操作。 + /// public LocalizationProvider(IServiceProvider sp, ILogger logger, IHttpClientFactory? httpClientFactory, IJSRuntime? jsRuntime, ILocalizationService localizationService) { _sp = sp; @@ -57,22 +71,22 @@ namespace Atomx.Admin.Client.Services _logger = logger; _localizationService = localizationService; - // 不在构造函数中进行 JS 相关同步 IO,但尝试根据线程 culture 设置初始值并归一化为完整 culture + // 尝试根据当前线程 culture 初始化 _currentCulture(不抛出异常) try { var threadUi = CultureInfo.DefaultThreadCurrentUICulture ?? CultureInfo.CurrentUICulture; if (!string.IsNullOrEmpty(threadUi?.Name)) { _currentCulture = MapToFullCulture(threadUi!.Name); - _logger?.LogDebug("LocalizationProvider ctor detected thread UI culture: {Culture}", _currentCulture); + _logger.LogDebug("LocalizationProvider 构造检测到线程 UI 文化: {Culture}", _currentCulture); } } catch (Exception ex) { - _logger?.LogDebug(ex, "LocalizationProvider ctor failed to read thread culture"); + _logger.LogDebug(ex, "LocalizationProvider 构造读取线程文化失败"); } - // 如果运行在 Server(IWebHostEnvironment 可用),且 JSRuntime 不可用(说明非浏览器),则同步从文件加载本地化文件 + // 如果在 Server 环境且未能使用 JSRuntime(意味着非浏览器),则尝试同步从文件系统读取本地化文件以支持 prerender try { var envType = Type.GetType("Microsoft.AspNetCore.Hosting.IWebHostEnvironment, Microsoft.AspNetCore.Hosting.Abstractions") @@ -93,23 +107,23 @@ namespace Atomx.Admin.Client.Services var json = File.ReadAllText(path); var dict = JsonSerializer.Deserialize>(json) ?? new Dictionary(); _cache[_currentCulture] = dict; - _logger?.LogInformation("Loaded localization file for {Culture} from path {Path}, entries: {Count}", _currentCulture, path, dict.Count); + _logger.LogInformation("(Server 同步) 从路径加载本地化文件 {Path},Culture:{Culture},条目数:{Count}", path, _currentCulture, dict.Count); } catch (Exception ex) { - _logger?.LogWarning(ex, "Failed to read localization file synchronously: {Path}", path); + _logger.LogWarning(ex, "(Server 同步) 读取本地化文件失败: {Path}", path); } } else { - _logger?.LogDebug("Localization file not found at {Path}", path); + _logger.LogDebug("本地化文件未在路径找到: {Path}", path); } } } } catch (Exception ex) { - _logger?.LogDebug(ex, "Synchronous file load attempt failed in ctor"); + _logger.LogDebug(ex, "LocalizationProvider 构造中同步文件加载尝试失败"); } } @@ -117,6 +131,9 @@ namespace Atomx.Admin.Client.Services public event EventHandler? LanguageChanged; + /// + /// 从缓存中读取指定键的本地化字符串,未找到返回 null。 + /// public string? GetString(string key) { if (string.IsNullOrEmpty(key)) return null; @@ -129,9 +146,14 @@ namespace Atomx.Admin.Client.Services return null; } + /// + /// 初始化流程(浏览器端) + /// 优先级:URL 语言前缀 -> Cookie(atomx.culture) -> 浏览器语言 -> 默认 + /// 若解析到短码(如 zh/en)则映射为完整文化(如 zh-Hans/en-US)。 + /// public async Task InitializeAsync() { - _logger?.LogDebug("LocalizationProvider.InitializeAsync start. CurrentCulture={Culture}", _currentCulture); + _logger.LogDebug("LocalizationProvider.InitializeAsync 开始. CurrentCulture={Culture}", _currentCulture); string? urlFirstSegment = null; @@ -140,7 +162,7 @@ namespace Atomx.Admin.Client.Services if (_jsRuntime != null && OperatingSystem.IsBrowser()) { var path = await _jsRuntime.InvokeAsync("eval", "location.pathname"); - _logger?.LogDebug("JS location.pathname='{Path}'", path); + _logger.LogDebug("JS location.pathname='{Path}'", path); if (!string.IsNullOrEmpty(path)) { var trimmed = path.Trim('/'); @@ -148,19 +170,19 @@ namespace Atomx.Admin.Client.Services { var seg = trimmed.Split('/', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(); urlFirstSegment = seg; - _logger?.LogDebug("Detected url first segment: {Segment}", urlFirstSegment); + _logger.LogDebug("检测到 URL 首段: {Segment}", urlFirstSegment); } } } } catch (Exception ex) { - _logger?.LogDebug(ex, "读取 location.pathname 失败"); + _logger.LogDebug(ex, "读取 location.pathname 失败"); } if (!string.IsNullOrEmpty(urlFirstSegment) && ShortToCulture.TryGetValue(urlFirstSegment, out var mapped)) { - _logger?.LogDebug("URL short segment '{Seg}' mapped to culture '{Culture}'", urlFirstSegment, mapped); + _logger.LogDebug("URL 短码 '{Seg}' 映射为文化 '{Culture}'", urlFirstSegment, mapped); await SetCultureInternalAsync(mapped, persistCookie: false); return; } @@ -170,18 +192,18 @@ namespace Atomx.Admin.Client.Services if (_jsRuntime != null && OperatingSystem.IsBrowser()) { var cookieVal = await _jsRuntime.InvokeAsync("cookies.Read", CookieName); - _logger?.LogDebug("Cookie '{CookieName}'='{CookieVal}'", CookieName, cookieVal); + _logger.LogDebug("读取 Cookie '{CookieName}'='{CookieVal}'", CookieName, cookieVal); if (!string.IsNullOrEmpty(cookieVal)) { if (ShortToCulture.TryGetValue(cookieVal, out var mappedFromCookie)) { - _logger?.LogDebug("Cookie short '{Cookie}' mapped to {Culture}", cookieVal, mappedFromCookie); + _logger.LogDebug("Cookie 短码 '{Cookie}' 映射为文化 {Culture}", cookieVal, mappedFromCookie); await SetCultureInternalAsync(mappedFromCookie, persistCookie: false); return; } else { - // 如果 cookie 中是完整 culture(或其他形式),归一化后使用 + // cookie 中可能已经是完整 culture,进行平滑归一化 await SetCultureInternalAsync(MapToFullCulture(cookieVal), persistCookie: false); return; } @@ -190,7 +212,7 @@ namespace Atomx.Admin.Client.Services } catch (Exception ex) { - _logger?.LogDebug(ex, "读取 Cookie 失败"); + _logger.LogDebug(ex, "读取 Cookie 失败"); } try @@ -198,11 +220,11 @@ namespace Atomx.Admin.Client.Services if (_jsRuntime != null && OperatingSystem.IsBrowser()) { var browserLang = await _jsRuntime.InvokeAsync("getBrowserLanguage"); - _logger?.LogDebug("Browser language: {BrowserLang}", browserLang); + _logger.LogDebug("浏览器语言: {BrowserLang}", browserLang); if (!string.IsNullOrEmpty(browserLang)) { var mappedFromBrowser = MapToFullCulture(browserLang); - _logger?.LogDebug("Browser mapped to {Culture}", mappedFromBrowser); + _logger.LogDebug("浏览器语言映射为 {Culture}", mappedFromBrowser); await SetCultureInternalAsync(mappedFromBrowser, persistCookie: false); return; } @@ -210,15 +232,18 @@ namespace Atomx.Admin.Client.Services } catch (Exception ex) { - _logger?.LogDebug(ex, "读取浏览器语言失败"); + _logger.LogDebug(ex, "读取浏览器语言失败"); } - // 最后确保加载当前 culture - _logger?.LogDebug("InitializeAsync falling back to current culture {Culture}", _currentCulture); + // 回退到当前默认文化并确保加载资源 + _logger.LogDebug("InitializeAsync 回退使用当前文化 {Culture}", _currentCulture); await EnsureCultureLoadedAsync(_currentCulture); await SetCultureInternalAsync(_currentCulture, persistCookie: false); } + /// + /// 对外异步设置文化(可传短码或完整 culture),并可选择是否持久化到 Cookie + /// public async Task SetCultureAsync(string cultureShortOrFull) { if (string.IsNullOrEmpty(cultureShortOrFull)) return; @@ -235,10 +260,9 @@ namespace Atomx.Admin.Client.Services public Task LoadCultureAsync(string culture) => EnsureCultureLoadedAsync(MapToFullCulture(culture)); /// - /// Server-side synchronous culture set used during prerender to ensure translations - /// are available immediately. This method will attempt to load localization - /// JSON from the server's webroot synchronously and set thread cultures. - /// + /// Server 端用于 prerender 时的同步设置:立即设置线程 Culture 并尝试从 webroot 同步加载本地化文件。 + /// 该方法不会触发 JS 操作,适合在中间件或请求处理早期使用。 + /// public void SetCultureForServer(string cultureShortOrFull) { try @@ -246,7 +270,7 @@ namespace Atomx.Admin.Client.Services var cultureFull = MapToFullCulture(cultureShortOrFull); if (string.IsNullOrEmpty(cultureFull)) return; - // set thread culture + // 设置线程 culture,影响后续服务端处理(例如 IStringLocalizer) try { var ci = new CultureInfo(cultureFull); @@ -256,7 +280,7 @@ namespace Atomx.Admin.Client.Services } catch { } - // try load from webroot synchronously via IWebHostEnvironment if available + // 同步从 webroot 加载 JSON 本地化文件(若存在) try { var envType = Type.GetType("Microsoft.AspNetCore.Hosting.IWebHostEnvironment, Microsoft.AspNetCore.Hosting.Abstractions") @@ -277,11 +301,11 @@ namespace Atomx.Admin.Client.Services var json = File.ReadAllText(path); var dict = JsonSerializer.Deserialize>(json) ?? new Dictionary(); _cache[cultureFull] = dict; - _logger?.LogInformation("(Server sync) Loaded localization file for {Culture} from path {Path}, entries: {Count}", cultureFull, path, dict.Count); + _logger.LogInformation("(Server 同步) 已为 {Culture} 从路径加载本地化文件,条目数: {Count}", cultureFull, dict.Count); } catch (Exception ex) { - _logger?.LogWarning(ex, "(Server sync) Failed to read localization file synchronously: {Path}", path); + _logger.LogWarning(ex, "(Server 同步) 读取本地化文件失败: {Path}", path); } } } @@ -289,18 +313,21 @@ namespace Atomx.Admin.Client.Services } catch (Exception ex) { - _logger?.LogDebug(ex, "SetCultureForServer failed to load file for {Culture}", cultureFull); + _logger.LogDebug(ex, "SetCultureForServer 加载文件时发生错误: {Culture}", cultureFull); } } catch (Exception ex) { - _logger?.LogDebug(ex, "SetCultureForServer encountered error"); + _logger.LogDebug(ex, "SetCultureForServer 执行过程中发生异常"); } } + /// + /// 内部设置文化并在需要时持久化 Cookie、更新 localization service 并触发事件。 + /// private async Task SetCultureInternalAsync(string cultureFull, bool persistCookie) { - //_logger?.LogDebug("设置内部文化异步开始: {Culture}, 持久化={Persist}", cultureFull, persistCookie); + //_logger.LogDebug("设置内部文化异步开始: {Culture}, 持久化={Persist}", cultureFull, persistCookie); await EnsureCultureLoadedAsync(cultureFull); try @@ -310,11 +337,11 @@ namespace Atomx.Admin.Client.Services CultureInfo.DefaultThreadCurrentUICulture = ci; _currentCulture = cultureFull; _localizationService.SetLanguage(ci); - _logger?.LogDebug("Culture set to {Culture}", cultureFull); + _logger.LogDebug("文化已设置为 {Culture}", cultureFull); } catch (Exception ex) { - _logger?.LogWarning(ex, "设置 Culture 失败: {Culture}", cultureFull); + _logger.LogWarning(ex, "设置 Culture 失败: {Culture}", cultureFull); } if (persistCookie && _jsRuntime != null) @@ -322,11 +349,12 @@ namespace Atomx.Admin.Client.Services try { var shortKey = ShortToCulture.FirstOrDefault(kv => string.Equals(kv.Value, cultureFull, StringComparison.OrdinalIgnoreCase)).Key ?? cultureFull; + // 将 shortKey 写入 cookie,供后端请求读取(例如 Server 模式下的中间件) await _jsRuntime.InvokeVoidAsync("cookies.Write", CookieName, shortKey, DateTime.UtcNow.AddYears(1).ToString("o")); } catch (Exception ex) { - _logger?.LogDebug(ex, "写 Cookie 失败"); + _logger.LogDebug(ex, "写入 Cookie 失败"); } } @@ -334,41 +362,46 @@ namespace Atomx.Admin.Client.Services { if (_jsRuntime != null) { + // 尝试设置 HTML 的 lang 属性,改善无障碍/SEO await _jsRuntime.InvokeVoidAsync("setHtmlLang", cultureFull); } } catch { } + // 通知订阅者 LanguageChanged?.Invoke(this, cultureFull); } + /// + /// 确保指定文化的 JSON 文件已被加载到缓存。加载顺序:WASM HttpClient -> 文件系统 -> 空字典占位。 + /// private async Task EnsureCultureLoadedAsync(string cultureFull) { - // Normalize possible short codes (e.g. zh -> zh-Hans, en -> en-US) and variants (zh-CN -> zh-Hans) + // 归一化短码(例如 zh -> zh-Hans) cultureFull = MapToFullCulture(cultureFull); if (string.IsNullOrEmpty(cultureFull)) return; if (_cache.ContainsKey(cultureFull)) { - _logger?.LogDebug("EnsureCultureLoadedAsync: culture {Culture} already cached", cultureFull); + _logger.LogDebug("EnsureCultureLoadedAsync: 文化 {Culture} 已缓存", cultureFull); return; } - // Prefer HttpClient when running in browser (WASM) + // 如果在浏览器(WASM),优先使用 HttpClient 下载本地化 JSON if (_jsRuntime != null && OperatingSystem.IsBrowser()) { - _logger?.LogInformation("EnsureCultureLoadedAsync: running in browser, will attempt HttpClient for {Culture}", cultureFull); + _logger.LogInformation("EnsureCultureLoadedAsync: 在浏览器环境,将尝试通过 HttpClient 下载 {Culture}", cultureFull); try { var http = _sp.GetService(typeof(HttpClient)) as HttpClient; if (http == null && _httpClientFactory != null) { - _logger?.LogDebug("HttpClient not found from service provider, using factory"); + _logger.LogDebug("未从 ServiceProvider 解析到 HttpClient,使用 IHttpClientFactory 创建"); http = _httpClientFactory.CreateClient(); } else { - _logger?.LogDebug("HttpClient resolved from service provider: {HasClient}", http != null); + _logger.LogDebug("从 ServiceProvider 解析 HttpClient: {HasClient}", http != null); } if (http != null) @@ -376,7 +409,7 @@ namespace Atomx.Admin.Client.Services var url = $"/localization/{cultureFull}.json"; Uri? requestUri = null; - // If HttpClient has a BaseAddress, use it. Otherwise, if running in browser, build absolute URI from location.origin + // 当 HttpClient 配置了 BaseAddress 时使用;否则通过 JS 获取 location.origin 构建绝对 URI if (http.BaseAddress != null) { requestUri = new Uri(http.BaseAddress, url); @@ -388,43 +421,42 @@ namespace Atomx.Admin.Client.Services var origin = await _jsRuntime.InvokeAsync("eval", "location.origin"); if (!string.IsNullOrEmpty(origin)) { - // ensure no double slashes requestUri = new Uri(new Uri(origin), url); } } catch (Exception jsEx) { - _logger?.LogDebug(jsEx, "Failed to get location.origin from JS"); + _logger.LogDebug(jsEx, "从 JS 获取 location.origin 失败"); } } if (requestUri != null) { - _logger?.LogInformation("Downloading localization from {Url}", requestUri); + _logger.LogInformation("从 {Url} 下载本地化资源", requestUri); var txt = await http.GetStringAsync(requestUri); var dict = JsonSerializer.Deserialize>(txt) ?? new Dictionary(); _cache[cultureFull] = dict; - _logger?.LogInformation("Loaded localization via HttpClient for {Culture}, entries: {Count}", cultureFull, dict.Count); + _logger.LogInformation("通过 HttpClient 为 {Culture} 加载到本地化数据,条目数: {Count}", cultureFull, dict.Count); return; } else { - _logger?.LogWarning("HttpClient has no BaseAddress and JSRuntime unavailable to construct absolute URL; skipping HttpClient load for {Culture}", cultureFull); + _logger.LogWarning("HttpClient 无法构建请求 URL,跳过通过 HttpClient 加载 {Culture}", cultureFull); } } else { - _logger?.LogWarning("No HttpClient available to load localization for {Culture}", cultureFull); + _logger.LogWarning("未找到可用的 HttpClient 以加载 {Culture}", cultureFull); } } catch (Exception ex) { - _logger?.LogDebug(ex, "通过 HttpClient 加载本地化文件失败: {Culture}", cultureFull); + _logger.LogDebug(ex, "通过 HttpClient 加载本地化文件失败: {Culture}", cultureFull); } } - _logger?.LogDebug("EnsureCultureLoadedAsync trying filesystem for {Culture}", cultureFull); - // 尝试通过反射获取 IWebHostEnvironment(Server 时可用) + _logger.LogDebug("EnsureCultureLoadedAsync: 尝试文件系统加载 {Culture}", cultureFull); + // 回退:尝试通过 IWebHostEnvironment 从文件系统读取(Server 时可用) try { var envType = Type.GetType("Microsoft.AspNetCore.Hosting.IWebHostEnvironment, Microsoft.AspNetCore.Hosting.Abstractions") @@ -438,67 +470,70 @@ namespace Atomx.Admin.Client.Services var contentRootProp = envType.GetProperty("ContentRootPath"); var webRoot = webRootProp?.GetValue(env) as string ?? Path.Combine(contentRootProp?.GetValue(env) as string ?? ".", "wwwroot"); var path = Path.Combine(webRoot ?? ".", "localization", cultureFull + ".json"); - _logger?.LogDebug("Looking for localization file at {Path}", path); + _logger.LogDebug("查找本地化文件路径: {Path}", path); if (File.Exists(path)) { var json = await File.ReadAllTextAsync(path); var dict = JsonSerializer.Deserialize>(json) ?? new Dictionary(); _cache[cultureFull] = dict; - _logger?.LogInformation("Loaded localization from file for {Culture}, entries: {Count}", cultureFull, dict.Count); + _logger.LogInformation("从文件为 {Culture} 加载本地化,条目数: {Count}", cultureFull, dict.Count); return; } else { - _logger?.LogDebug("Localization file not found at {Path}", path); - // Fallback: check build output wwwroot under AppContext.BaseDirectory + _logger.LogDebug("未在路径找到本地化文件: {Path}", path); + // 备用路径:构建于运行时输出目录的 wwwroot try { var alt = Path.Combine(AppContext.BaseDirectory ?? ".", "wwwroot", "localization", cultureFull + ".json"); - _logger?.LogDebug("Looking for localization file at alternative path {AltPath}", alt); + _logger.LogDebug("查找备用路径: {AltPath}", alt); if (File.Exists(alt)) { var json2 = await File.ReadAllTextAsync(alt); var dict2 = JsonSerializer.Deserialize>(json2) ?? new Dictionary(); _cache[cultureFull] = dict2; - _logger?.LogInformation("Loaded localization from alternative file path for {Culture}, entries: {Count}", cultureFull, dict2.Count); + _logger.LogInformation("从备用路径为 {Culture} 加载到本地化文件,条目数: {Count}", cultureFull, dict2.Count); return; } else { - _logger?.LogDebug("Localization file not found at alternative path {AltPath}", alt); + _logger.LogDebug("备用路径未找到本地化文件: {AltPath}", alt); } } catch (Exception exAlt) { - _logger?.LogDebug(exAlt, "Error while checking alternative localization path"); + _logger.LogDebug(exAlt, "检查备用本地化路径时出错"); } } } else { - _logger?.LogDebug("IWebHostEnvironment not resolved from service provider"); + _logger.LogDebug("无法从 ServiceProvider 获取 IWebHostEnvironment 实例"); } } else { - _logger?.LogDebug("IWebHostEnvironment type not found via reflection"); + _logger.LogDebug("通过反射未能找到 IWebHostEnvironment 类型"); } } catch (Exception ex) { - _logger?.LogDebug(ex, "从文件系统加载本地化文件失败: {Culture}", cultureFull); + _logger.LogDebug(ex, "通过文件系统加载本地化文件失败: {Culture}", cultureFull); } - _logger?.LogDebug("EnsureCultureLoadedAsync fallback to empty dict for {Culture}", cultureFull); + _logger.LogDebug("EnsureCultureLoadedAsync: 回退为 {Culture} 的空字典占位", cultureFull); _cache[cultureFull] = new Dictionary(); } + /// + /// 将短码或带区域的字符串映射为内部使用的完整 culture(例如 zh -> zh-Hans) + /// private string MapToFullCulture(string culture) { if (string.IsNullOrEmpty(culture)) return culture; - // direct mapping + // 直接映射 if (ShortToCulture.TryGetValue(culture, out var mapped)) return mapped; - // consider prefix, e.g. zh-CN -> zh + // 考虑前缀,例如 zh-CN -> zh var prefix = culture.Split('-', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(); if (!string.IsNullOrEmpty(prefix) && ShortToCulture.TryGetValue(prefix, out var mapped2)) return mapped2; return culture; diff --git a/Atomx.Admin/Atomx.Admin.Client/Services/WasmLocalizationProvider.cs b/Atomx.Admin/Atomx.Admin.Client/Services/WasmLocalizationProvider.cs deleted file mode 100644 index cd45f04..0000000 --- a/Atomx.Admin/Atomx.Admin.Client/Services/WasmLocalizationProvider.cs +++ /dev/null @@ -1,239 +0,0 @@ -using System.Collections.Concurrent; -using System.Globalization; -using System.Text.Json; -using Microsoft.Extensions.Logging; -using Microsoft.JSInterop; -using Blazored.LocalStorage; - -namespace Atomx.Admin.Client.Services -{ - public class WasmLocalizationProvider : ILocalizationProvider - { - private readonly IJSRuntime _jsRuntime; - 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"; - private bool _isInitialized = false; - - private const string LocalizationStorageKey = "Localization_{0}"; - private const string LocalizationVersionKey = "LocalizationVersion_{0}"; - - 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; - public bool IsInitialized => _isInitialized; - - public event EventHandler? LanguageChanged; - - public string? GetString(string key) - { - if (string.IsNullOrEmpty(key)) return null; - - if (_cache.TryGetValue(_currentCulture, out var dict) && dict.TryGetValue(key, out var val)) - { - return val; - } - - return null; - } - - public async Task InitializeAsync() - { - 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) - { - 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); - } - - public async Task LoadCultureAsync(string culture) - { - var cultureFull = MapToFullCulture(culture); - - // Step 1: Check in-memory cache - if (_cache.ContainsKey(cultureFull)) return; - - lock (_loadingCultures) - { - if (_loadingCultures.Contains(cultureFull)) - { - _logger.LogDebug("Culture {Culture} is already being loaded.", cultureFull); - return; - } - _loadingCultures.Add(cultureFull); - } - - try - { - // Step 2: Check local storage for cached data - var localDataKey = string.Format(LocalizationStorageKey, cultureFull); - var localVersionKey = string.Format(LocalizationVersionKey, cultureFull); - - var cachedVersion = await _localStorage.GetItemAsync(localVersionKey); - var cachedData = await _localStorage.GetItemAsync>(localDataKey); - - if (cachedData != null) - { - _cache[cultureFull] = cachedData; - _logger.LogInformation("Loaded localization for {Culture} from local storage.", cultureFull); - } - - // Step 3: Validate version with server - var versionUrl = $"api/localeresource/version/{cultureFull}"; - var serverVersion = await _httpClient.GetStringAsync(versionUrl); - - 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; - } - - // Step 4: Fetch from server if version mismatch or no cached data - var url = $"/localization/{cultureFull}.json"; - var json = await _httpClient.GetStringAsync(url); - var dict = JsonSerializer.Deserialize>(json) ?? new Dictionary(); - _cache[cultureFull] = dict; - - // Step 5: Update local storage - await _localStorage.SetItemAsync(localVersionKey, serverVersion); - 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) - { - _logger.LogError(ex, "Failed to load localization file for {Culture}", cultureFull); - } - finally - { - lock (_loadingCultures) - { - _loadingCultures.Remove(cultureFull); - } - } - - // Notify listeners that the culture has been loaded - LanguageChanged?.Invoke(this, cultureFull); - } - - private string MapToFullCulture(string culture) - { - return culture switch - { - "zh" => "zh-Hans", - "en" => "en-US", - _ => culture - }; - } - - /// - /// 从服务器获取本地化数据 - /// - /// - /// - private Dictionary FetchFromServer(string culture) - { - var url = $"/localization/{culture}.json"; - var json = _httpClient.GetStringAsync(url).Result; - var dict = JsonSerializer.Deserialize>(json) ?? new Dictionary(); - return dict; - } - - /// - /// 检查本地化版本是否最新 - /// - /// - /// - /// - private async Task CheckVersionAsync(string versionKey, string culture) - { - var cachedVersion = await _localStorage.GetItemAsync(versionKey); - if(string.IsNullOrEmpty(cachedVersion)) - { - return false; - } - var versionUrl = $"api/localeresource/version/{culture}"; - var serverVersion = await _httpClient.GetStringAsync(versionUrl); - - if (cachedVersion != serverVersion) - { - return false; - } - return true; - } - } -} \ No newline at end of file diff --git a/Atomx.Admin/Atomx.Admin/Controllers/LocaleResourceController.cs b/Atomx.Admin/Atomx.Admin/Controllers/LocaleResourceController.cs index e14a0eb..f4b985e 100644 --- a/Atomx.Admin/Atomx.Admin/Controllers/LocaleResourceController.cs +++ b/Atomx.Admin/Atomx.Admin/Controllers/LocaleResourceController.cs @@ -12,7 +12,6 @@ using Atomx.Utils.Models; using MapsterMapper; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Hosting; namespace Atomx.Admin.Controllers { @@ -206,17 +205,65 @@ namespace Atomx.Admin.Controllers } //寮傛鏇存柊瀵瑰簲鐨刯son鏂囦欢 - var wwwroot = _environment.WebRootPath; - var dic = new Dictionary(); - dic.Add(data.Name,data.Value); - - _backgroundService.UpdateLocalizationFile(wwwroot, model.Culture, dic.ToJson()); + var language = await _cacheService.GetLanguageById(model.LanguageId); + var culture = language?.Culture ?? string.Empty; + if (!string.IsNullOrEmpty(culture)) + { + var wwwroot = _environment.WebRootPath; + var path = Path.Combine(wwwroot, "localization"); + var dic = new Dictionary(); + dic.Add(data.Name, data.Value); + _backgroundService.UpdateLocalizationFile(path, culture, dic.ToJson()); + } result = result.IsSuccess(true); return new JsonResult(result); } + /// + /// 鍒犻櫎澶氳瑷缈昏瘧鏁版嵁 + /// + /// + /// + [HttpPost("delete")] + public async Task DeleteAsync(long id) + { + var result = new ApiResult(); + var data = _dbContext.LocaleResources.SingleOrDefault(p => p.Id == id); + if (data == null) + { + result = result.IsFail("鏁版嵁涓嶅瓨鍦紝璇锋洿鎹紒"); + return new JsonResult(result); + } + _dbContext.LocaleResources.Remove(data); + _dbContext.SaveChanges(); + result = result.IsSuccess(true); + return new JsonResult(result); + } + + /// + /// 閲嶆瀯澶氳瑷鏂囦欢 + /// + /// + /// + [HttpPost("rebuild")] + public async Task RebuidCultureFile(long id) + { + var result = new ApiResult(); + var data = _dbContext.Languages.SingleOrDefault(p => p.Id == id); + if (data == null) + { + result = result.IsFail("鏁版嵁涓嶅瓨鍦紝璇锋洿鎹紒"); + return new JsonResult(result); + } + var wwwroot = _environment.WebRootPath; + var path = Path.Combine(wwwroot, "localization"); + _backgroundService.RebuildLocalizationFile(path, data.Culture); + result = result.IsSuccess(true); + return new JsonResult(result); + } + /// /// 鑾峰彇瀵瑰簲鐨勫璇█鏂囧寲鏁版嵁鐨勭増鏈 /// diff --git a/Atomx.Admin/Atomx.Admin/Extensions/DbMigrateExtension.cs b/Atomx.Admin/Atomx.Admin/Extensions/DbMigrateExtension.cs index 8c82c12..c7d18b4 100644 --- a/Atomx.Admin/Atomx.Admin/Extensions/DbMigrateExtension.cs +++ b/Atomx.Admin/Atomx.Admin/Extensions/DbMigrateExtension.cs @@ -60,7 +60,6 @@ namespace Atomx.Admin.Extensions language.Culture = "zh-cn"; language.CreateTime = DateTime.UtcNow; language.UpdateTime = DateTime.UtcNow; - language.Id = 1; language.Name = "涓枃"; language.DisplayOrder = 0; language.Enabled = true; diff --git a/Atomx.Admin/Atomx.Admin/Middlewares/RequestCultureMiddleware.cs b/Atomx.Admin/Atomx.Admin/Middlewares/RequestCultureMiddleware.cs index 39eba59..615b7ab 100644 --- a/Atomx.Admin/Atomx.Admin/Middlewares/RequestCultureMiddleware.cs +++ b/Atomx.Admin/Atomx.Admin/Middlewares/RequestCultureMiddleware.cs @@ -61,7 +61,7 @@ namespace Atomx.Admin.Middlewares { logger?.LogDebug("ILocalizationProvider not registered in RequestServices"); } - else if (providerObj is Atomx.Admin.Services.ServerLocalizationProvider provider) + else if (providerObj is Atomx.Admin.Client.Services.LocalizationProvider provider) { logger?.LogDebug("Calling SetCultureForServer on LocalizationProvider with {Culture}", cultureName); _= provider.SetCultureAsync(cultureName); diff --git a/Atomx.Admin/Atomx.Admin/Program.cs b/Atomx.Admin/Atomx.Admin/Program.cs index 65b8a85..149b5d7 100644 --- a/Atomx.Admin/Atomx.Admin/Program.cs +++ b/Atomx.Admin/Atomx.Admin/Program.cs @@ -68,7 +68,7 @@ builder.Services.AddHttpContextAccessor(); builder.Services.AddHttpClientApiService(builder.Configuration["WebApi:ServerUrl"] ?? "http://localhost"); // 注入本地化提供程序与服务 -builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddTransient(typeof(IStringLocalizer<>), typeof(JsonStringLocalizer<>)); diff --git a/Atomx.Admin/Atomx.Admin/Services/ServerLocalizationProvider.cs b/Atomx.Admin/Atomx.Admin/Services/ServerLocalizationProvider.cs deleted file mode 100644 index b35f3e6..0000000 --- a/Atomx.Admin/Atomx.Admin/Services/ServerLocalizationProvider.cs +++ /dev/null @@ -1,86 +0,0 @@ -using Atomx.Admin.Client.Services; -using System.Collections.Concurrent; -using System.Text.Json; - -namespace Atomx.Admin.Services -{ - public class ServerLocalizationProvider : ILocalizationProvider - { - private readonly IServiceProvider _serviceProvider; - private readonly ILogger _logger; - private static readonly ConcurrentDictionary> _cache = new(); - private string _currentCulture = "zh-Hans"; - - public ServerLocalizationProvider(IServiceProvider serviceProvider, ILogger logger) - { - _serviceProvider = serviceProvider; - _logger = logger; - } - - public string CurrentCulture => _currentCulture; - - public event EventHandler? LanguageChanged; - - public string? GetString(string key) - { - if (string.IsNullOrEmpty(key)) return null; - - if (_cache.TryGetValue(_currentCulture, out var dict) && dict.TryGetValue(key, out var val)) - { - return val; - } - - return null; - } - - public async Task InitializeAsync() - { - await LoadCultureAsync(_currentCulture); - } - - public async Task SetCultureAsync(string cultureShortOrFull) - { - _currentCulture = MapToFullCulture(cultureShortOrFull); - await LoadCultureAsync(_currentCulture); - LanguageChanged?.Invoke(this, _currentCulture); - } - - public Task LoadCultureAsync(string culture) - { - var cultureFull = MapToFullCulture(culture); - if (_cache.ContainsKey(cultureFull)) return Task.CompletedTask; - - try - { - var env = _serviceProvider.GetService(typeof(Microsoft.AspNetCore.Hosting.IWebHostEnvironment)) as Microsoft.AspNetCore.Hosting.IWebHostEnvironment; - if (env != null) - { - var path = Path.Combine(env.WebRootPath, "localization", cultureFull + ".json"); - if (File.Exists(path)) - { - var json = File.ReadAllText(path); - var dict = JsonSerializer.Deserialize>(json) ?? new Dictionary(); - _cache[cultureFull] = dict; - _logger.LogInformation("Loaded localization file for {Culture} from {Path}", cultureFull, path); - } - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to load localization file for {Culture}", cultureFull); - } - - return Task.CompletedTask; - } - - private string MapToFullCulture(string culture) - { - return culture switch - { - "zh" => "zh-Hans", - "en" => "en-US", - _ => culture - }; - } - } -} \ No newline at end of file diff --git a/Atomx.Common/Atomx.Common.csproj b/Atomx.Common/Atomx.Common.csproj index 22d26ce..b22bc6b 100644 --- a/Atomx.Common/Atomx.Common.csproj +++ b/Atomx.Common/Atomx.Common.csproj @@ -6,6 +6,12 @@ enable + + + + + + @@ -14,8 +20,4 @@ - - - - diff --git a/Atomx.Common/Entities/Address.cs b/Atomx.Common/Entities/Address.cs index 7054f47..6367e98 100644 --- a/Atomx.Common/Entities/Address.cs +++ b/Atomx.Common/Entities/Address.cs @@ -30,7 +30,7 @@ namespace Atomx.Common.Entities /// /// 閭欢鍦板潃 /// - [Column(TypeName = "varchar(255)")] + [Column(TypeName = "varchar(256)")] public string Email { get; set; } = string.Empty; /// @@ -42,7 +42,7 @@ namespace Atomx.Common.Entities /// /// 鍏徃 /// - [Column(TypeName = "varchar(255)")] + [Column(TypeName = "varchar(256)")] public string Company { get; set; } = string.Empty; /// diff --git a/Atomx.Common/Entities/Area.cs b/Atomx.Common/Entities/Area.cs index 510e251..9977090 100644 --- a/Atomx.Common/Entities/Area.cs +++ b/Atomx.Common/Entities/Area.cs @@ -24,7 +24,7 @@ namespace Atomx.Common.Entities /// 鍦板尯鍚嶇О /// - [Column(TypeName = "varchar(255)")] + [Column(TypeName = "varchar(256)")] public string Name { get; set; } = string.Empty; /// diff --git a/Atomx.Common/Entities/Category.cs b/Atomx.Common/Entities/Category.cs index de3e505..4015c14 100644 --- a/Atomx.Common/Entities/Category.cs +++ b/Atomx.Common/Entities/Category.cs @@ -45,31 +45,31 @@ namespace Atomx.Common.Entities /// /// Meta鎻忚堪浠嬬粛 /// - [Column(TypeName = "varchar(255)")] + [Column(TypeName = "varchar(256)")] public string MetaDescription { get; set; } = string.Empty; /// /// Meta鍏抽敭璇 /// - [Column(TypeName = "varchar(255)")] + [Column(TypeName = "varchar(256)")] public string MetaKeywords { get; set; } = string.Empty; /// /// 杩囨护灞炴Ds /// - [Column(TypeName = "varchar(255)")] + [Column(TypeName = "varchar(256)")] public string FilterAttributes { get; set; } = string.Empty; /// /// 鍒嗙被鍥剧墖 /// - [Column(TypeName = "varchar(255)")] + [Column(TypeName = "varchar(256)")] public string Image { get; set; } = string.Empty; /// /// 鍒嗙被椤 banner /// - [Column(TypeName = "varchar(255)")] + [Column(TypeName = "varchar(256)")] public string Banner { get; set; } = string.Empty; /// diff --git a/Atomx.Common/Entities/Language.cs b/Atomx.Common/Entities/Language.cs index 82a23b6..9286d87 100644 --- a/Atomx.Common/Entities/Language.cs +++ b/Atomx.Common/Entities/Language.cs @@ -37,7 +37,7 @@ namespace Atomx.Common.Entities /// /// 鍥芥棗 /// - [Column(TypeName = "varchar(255)")] + [Column(TypeName = "varchar(256)")] public string FlagImage { get; set; } = string.Empty; /// @@ -53,7 +53,7 @@ namespace Atomx.Common.Entities /// /// 澶氳瑷璧勬簮鐨勭増鏈紝鍙互鏄椂闂存埑鎴栧搱甯 /// - [Column(TypeName = "varchar(25)")] + [Column(TypeName = "varchar(256)")] public string ResourceVersion { get; set; } = string.Empty; /// diff --git a/Atomx.Common/Entities/LocaleResource.cs b/Atomx.Common/Entities/LocaleResource.cs index 14cc8f2..7992271 100644 --- a/Atomx.Common/Entities/LocaleResource.cs +++ b/Atomx.Common/Entities/LocaleResource.cs @@ -24,7 +24,7 @@ namespace Atomx.Common.Entities /// /// 璧勬簮鍚嶇О /// - [Column(TypeName = "varchar(255)")] + [Column(TypeName = "varchar(256)")] public string Name { get; set; } = string.Empty; /// diff --git a/Atomx.Common/Entities/LocalizedProperty.cs b/Atomx.Common/Entities/LocalizedProperty.cs index 73b6701..bdd0a53 100644 --- a/Atomx.Common/Entities/LocalizedProperty.cs +++ b/Atomx.Common/Entities/LocalizedProperty.cs @@ -34,7 +34,7 @@ namespace Atomx.Common.Entities /// /// 鏁版嵁KEY /// - [Column(TypeName = "varchar(255)")] + [Column(TypeName = "varchar(256)")] public string Key { get; set; } = string.Empty; /// diff --git a/Atomx.Common/Entities/Manufacturer.cs b/Atomx.Common/Entities/Manufacturer.cs index 4bfc72a..74d981c 100644 --- a/Atomx.Common/Entities/Manufacturer.cs +++ b/Atomx.Common/Entities/Manufacturer.cs @@ -48,7 +48,7 @@ namespace Atomx.Common.Entities /// /// Meta鏍囬 /// - [Column(TypeName = "varchar(255)")] + [Column(TypeName = "varchar(256)")] public string MetaKeywords { get; set; } = string.Empty; /// diff --git a/Atomx.Common/Entities/MaterialRecord.cs b/Atomx.Common/Entities/MaterialRecord.cs index 78efa20..1c594db 100644 --- a/Atomx.Common/Entities/MaterialRecord.cs +++ b/Atomx.Common/Entities/MaterialRecord.cs @@ -69,7 +69,7 @@ namespace Atomx.Common.Entities /// /// 澶囨敞璇存槑 /// - [Column(TypeName = "varchar(255)")] + [Column(TypeName = "varchar(256)")] public string Note { get; set; } = string.Empty; /// diff --git a/Atomx.Common/Entities/Menu.cs b/Atomx.Common/Entities/Menu.cs index 069f38b..40ac652 100644 --- a/Atomx.Common/Entities/Menu.cs +++ b/Atomx.Common/Entities/Menu.cs @@ -35,7 +35,7 @@ namespace Atomx.Common.Entities /// /// 鍒嗙被鍥炬爣 /// - [Column(TypeName = "varchar(255)")] + [Column(TypeName = "varchar(256)")] public string Icon { get; set; } = string.Empty; /// @@ -47,7 +47,7 @@ namespace Atomx.Common.Entities /// /// 鑿滃崟閾炬帴鐨刄RL /// - [Column(TypeName = "varchar(255)")] + [Column(TypeName = "varchar(256)")] public string Url { get; set; } = string.Empty; /// diff --git a/Atomx.Common/Entities/Order.cs b/Atomx.Common/Entities/Order.cs index 1d27c0e..d5272cf 100644 --- a/Atomx.Common/Entities/Order.cs +++ b/Atomx.Common/Entities/Order.cs @@ -75,7 +75,7 @@ namespace Atomx.Common.Entities /// /// 閭欢鍦板潃 /// - [Column(TypeName = "varchar(255)")] + [Column(TypeName = "varchar(256)")] public string Email { get; set; } = string.Empty; /// @@ -87,7 +87,7 @@ namespace Atomx.Common.Entities /// /// 鍏徃 /// - [Column(TypeName = "varchar(255)")] + [Column(TypeName = "varchar(256)")] public string Company { get; set; } = string.Empty; /// @@ -303,13 +303,13 @@ namespace Atomx.Common.Entities /// /// 璁㈠崟澶囨敞璇存槑 /// - [Column(TypeName = "varchar(255)")] + [Column(TypeName = "varchar(256)")] public string? Remark { get; set; } = string.Empty; /// /// 鍗栧涓嬪崟澶囨敞淇℃伅 /// - [Column(TypeName = "varchar(255)")] + [Column(TypeName = "varchar(256)")] public string? Message { get; set; } = string.Empty; /// diff --git a/Atomx.Common/Entities/OrderItem.cs b/Atomx.Common/Entities/OrderItem.cs index 66a4803..9955236 100644 --- a/Atomx.Common/Entities/OrderItem.cs +++ b/Atomx.Common/Entities/OrderItem.cs @@ -74,7 +74,7 @@ namespace Atomx.Common.Entities /// /// 鍟嗗搧鍚嶇О /// - [Column(TypeName = "varchar(255)")] + [Column(TypeName = "varchar(256)")] public string Title { get; set; } = string.Empty; /// diff --git a/Atomx.Common/Entities/PaymentChannel.cs b/Atomx.Common/Entities/PaymentChannel.cs index d88976e..68cb92a 100644 --- a/Atomx.Common/Entities/PaymentChannel.cs +++ b/Atomx.Common/Entities/PaymentChannel.cs @@ -44,7 +44,7 @@ namespace Atomx.Common.Entities /// /// 鏀舵璐﹀彿 /// - [Column(TypeName = "varchar(255)")] + [Column(TypeName = "varchar(256)")] public string Account { get; set; } = string.Empty; /// diff --git a/Atomx.Common/Entities/Permission.cs b/Atomx.Common/Entities/Permission.cs index d9894c4..9656eb9 100644 --- a/Atomx.Common/Entities/Permission.cs +++ b/Atomx.Common/Entities/Permission.cs @@ -22,7 +22,7 @@ namespace Atomx.Common.Entities /// /// 鏉冮檺鐐硅鏄 /// - [Column(TypeName = "varchar(255)")] + [Column(TypeName = "varchar(256)")] public string Description { get; set; } = string.Empty; /// diff --git a/Atomx.Common/Entities/Product.cs b/Atomx.Common/Entities/Product.cs index fe4bd5d..1968ac9 100644 --- a/Atomx.Common/Entities/Product.cs +++ b/Atomx.Common/Entities/Product.cs @@ -56,7 +56,7 @@ namespace Atomx.Common.Entities /// /// 浜у搧鍚嶇О /// - [Column(TypeName = "varchar(255)")] + [Column(TypeName = "varchar(256)")] public string Title { get; set; } = string.Empty; /// @@ -80,13 +80,13 @@ namespace Atomx.Common.Entities /// /// 浜у搧鍗栫偣 /// - [Column(TypeName = "varchar(255)")] + [Column(TypeName = "varchar(256)")] public string? Feature { get; set; } = string.Empty; /// /// 浜у搧绠浠 /// - [Column(TypeName = "varchar(255)")] + [Column(TypeName = "varchar(256)")] public string? Description { get; set; } = string.Empty; /// diff --git a/Atomx.Common/Entities/ProductAttributeCombination.cs b/Atomx.Common/Entities/ProductAttributeCombination.cs index 80309f3..9b7f113 100644 --- a/Atomx.Common/Entities/ProductAttributeCombination.cs +++ b/Atomx.Common/Entities/ProductAttributeCombination.cs @@ -110,7 +110,7 @@ namespace Atomx.Common.Entities /// /// 澶囨敞璇存槑 /// - [Column(TypeName = "varchar(255)")] + [Column(TypeName = "varchar(256)")] public string Note { get; set; } = string.Empty; /// diff --git a/Atomx.Common/Entities/ProductAttributeValue.cs b/Atomx.Common/Entities/ProductAttributeValue.cs index ebc7382..a3a1b07 100644 --- a/Atomx.Common/Entities/ProductAttributeValue.cs +++ b/Atomx.Common/Entities/ProductAttributeValue.cs @@ -40,7 +40,7 @@ namespace Atomx.Common.Entities /// /// 灞炴у浘鐗 /// - [Column(TypeName = "varchar(255)")] + [Column(TypeName = "varchar(256)")] public string Image { get; set; } = string.Empty; /// diff --git a/Atomx.Common/Entities/ProductInventoryLog.cs b/Atomx.Common/Entities/ProductInventoryLog.cs index 32b3e39..837b05a 100644 --- a/Atomx.Common/Entities/ProductInventoryLog.cs +++ b/Atomx.Common/Entities/ProductInventoryLog.cs @@ -85,7 +85,7 @@ namespace Atomx.Common.Entities /// /// 澶囨敞璇存槑 /// - [Column(TypeName = "varchar(255)")] + [Column(TypeName = "varchar(256)")] public string Note { get; set; } = string.Empty; /// diff --git a/Atomx.Common/Entities/ProductListing.cs b/Atomx.Common/Entities/ProductListing.cs index e626131..df77622 100644 --- a/Atomx.Common/Entities/ProductListing.cs +++ b/Atomx.Common/Entities/ProductListing.cs @@ -50,7 +50,7 @@ namespace Atomx.Common.Entities /// /// SEO Title /// - [Column(TypeName = "varchar(255)")] + [Column(TypeName = "varchar(256)")] public string MetaTitle { get; set; } = string.Empty; /// diff --git a/Atomx.Common/Entities/Role.cs b/Atomx.Common/Entities/Role.cs index 43c80ae..6cacf0b 100644 --- a/Atomx.Common/Entities/Role.cs +++ b/Atomx.Common/Entities/Role.cs @@ -29,7 +29,7 @@ namespace Atomx.Common.Entities /// /// 璇存槑 /// - [Column(TypeName = "varchar(255)")] + [Column(TypeName = "varchar(256)")] public string Description { get; set; } = string.Empty; /// diff --git a/Atomx.Common/Entities/UserMeta.cs b/Atomx.Common/Entities/UserMeta.cs index f235770..b14384c 100644 --- a/Atomx.Common/Entities/UserMeta.cs +++ b/Atomx.Common/Entities/UserMeta.cs @@ -21,7 +21,7 @@ namespace Atomx.Common.Entities /// /// 瀛楁KEY /// - [Column(TypeName = "varchar(255)")] + [Column(TypeName = "varchar(256)")] public string MetaKey { get; set; } = string.Empty; /// diff --git a/Atomx.Common/Entities/WarehouseStockRecord.cs b/Atomx.Common/Entities/WarehouseStockRecord.cs index a30574f..ece6fc9 100644 --- a/Atomx.Common/Entities/WarehouseStockRecord.cs +++ b/Atomx.Common/Entities/WarehouseStockRecord.cs @@ -29,7 +29,7 @@ namespace Atomx.Common.Entities /// /// 澶囨敞璇存槑 /// - [Column(TypeName = "varchar(255)")] + [Column(TypeName = "varchar(256)")] public string Note { get; set; } = string.Empty; /// diff --git a/Atomx.Core/Jos/LocalizationJob.cs b/Atomx.Core/Jos/LocalizationJob.cs index b2a16c3..8076f58 100644 --- a/Atomx.Core/Jos/LocalizationJob.cs +++ b/Atomx.Core/Jos/LocalizationJob.cs @@ -34,63 +34,97 @@ namespace Atomx.Core.Jos try { var translations = data.FromJson>(); - - var fileName = $"{culture}.json"; - var filePath = Path.Combine(path, fileName); - if (!Directory.Exists(path)) + if (translations == null) { - Directory.CreateDirectory(path); - _logger.LogInformation("Created Resources directory: {Path}", path); + _logger.LogError("No translations provided for culture: {Culture}", culture); } - - var fileData = new Dictionary(); - if (File.Exists(filePath)) + else { - var json = await File.ReadAllTextAsync(filePath); - fileData = JsonSerializer.Deserialize>(json, new JsonSerializerOptions + + var fileName = $"{culture}.json"; + var filePath = Path.Combine(path, fileName); + if (!Directory.Exists(path)) { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - }) ?? new Dictionary(); + 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); } - - 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(); - } - 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); + } } } diff --git a/Atomx.Core/Services/BackgroundJobService.cs b/Atomx.Core/Services/BackgroundJobService.cs index dd94c63..b87c016 100644 --- a/Atomx.Core/Services/BackgroundJobService.cs +++ b/Atomx.Core/Services/BackgroundJobService.cs @@ -13,6 +13,16 @@ namespace Atomx.Core.Jos /// /// string UpdateLocalizationFile(string path, string culture, string data); + + /// + /// 閲嶆瀯缈昏瘧鏈湴鍖栨枃浠 + /// + /// + /// + /// + string RebuildLocalizationFile(string path, string culture); + + string SendSMSVerificationCode(string phoneNumber, string code, TimeSpan validDuration); } @@ -41,6 +51,18 @@ namespace Atomx.Core.Jos return jobId; } + /// + /// 閲嶆瀯缈昏瘧鏈湴鍖栨枃浠 + /// + /// + /// + /// + public string RebuildLocalizationFile(string path, string culture) + { + var jobId = _backgroundJobClient.Enqueue(job => job.RebuildCultureFile(path, culture)); + return jobId; + } + public string SendSMSVerificationCode(string phoneNumber, string code, TimeSpan validDuration) { return string.Empty; diff --git a/Atomx.Data/Migrations/20251203190956_0.1.Designer.cs b/Atomx.Data/Migrations/20251214094758_0.1.Designer.cs similarity index 97% rename from Atomx.Data/Migrations/20251203190956_0.1.Designer.cs rename to Atomx.Data/Migrations/20251214094758_0.1.Designer.cs index f00a3d1..88220ac 100644 --- a/Atomx.Data/Migrations/20251203190956_0.1.Designer.cs +++ b/Atomx.Data/Migrations/20251214094758_0.1.Designer.cs @@ -12,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Atomx.Data.Migrations { [DbContext(typeof(DataContext))] - [Migration("20251203190956_0.1")] + [Migration("20251214094758_0.1")] partial class _01 { /// @@ -20,7 +20,7 @@ namespace Atomx.Data.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "10.0.0") + .HasAnnotation("ProductVersion", "10.0.1") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -43,7 +43,7 @@ namespace Atomx.Data.Migrations b.Property("Company") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("Count") .HasColumnType("integer"); @@ -60,7 +60,7 @@ namespace Atomx.Data.Migrations b.Property("Email") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("FullAddress") .IsRequired() @@ -254,7 +254,7 @@ namespace Atomx.Data.Migrations b.Property("Name") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("NumericISOCode") .HasColumnType("integer"); @@ -282,7 +282,7 @@ namespace Atomx.Data.Migrations b.Property("Banner") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("Count") .HasColumnType("integer"); @@ -301,22 +301,22 @@ namespace Atomx.Data.Migrations b.Property("FilterAttributes") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("Image") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("IsNode") .HasColumnType("boolean"); b.Property("MetaDescription") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("MetaKeywords") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("Name") .IsRequired() @@ -470,7 +470,7 @@ namespace Atomx.Data.Migrations b.Property("FlagImage") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("Name") .IsRequired() @@ -478,7 +478,7 @@ namespace Atomx.Data.Migrations b.Property("ResourceVersion") .IsRequired() - .HasColumnType("varchar(25)"); + .HasColumnType("varchar(256)"); b.Property("Title") .IsRequired() @@ -502,7 +502,7 @@ namespace Atomx.Data.Migrations b.Property("Name") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("UpdateTime") .HasColumnType("timestamptz"); @@ -526,7 +526,7 @@ namespace Atomx.Data.Migrations b.Property("Key") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("LanguageNumber") .HasColumnType("integer"); @@ -566,7 +566,7 @@ namespace Atomx.Data.Migrations b.Property("Icon") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("IsLink") .HasColumnType("boolean"); @@ -594,7 +594,7 @@ namespace Atomx.Data.Migrations b.Property("Url") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.HasKey("Id"); @@ -646,7 +646,7 @@ namespace Atomx.Data.Migrations b.Property("Account") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("Config") .IsRequired() @@ -698,7 +698,7 @@ namespace Atomx.Data.Migrations b.Property("Description") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("Name") .IsRequired() @@ -736,7 +736,7 @@ namespace Atomx.Data.Migrations .HasColumnType("timestamptz"); b.Property("Description") - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("DisplayOrder") .HasColumnType("integer"); @@ -746,7 +746,7 @@ namespace Atomx.Data.Migrations .HasColumnType("text"); b.Property("Feature") - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("Image") .HasColumnType("varchar(256)"); @@ -804,7 +804,7 @@ namespace Atomx.Data.Migrations b.Property("Title") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("UpdateTime") .HasColumnType("timestamptz"); @@ -890,7 +890,7 @@ namespace Atomx.Data.Migrations b.Property("Note") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("Price") .HasColumnType("decimal(18,4)"); @@ -1001,7 +1001,7 @@ namespace Atomx.Data.Migrations b.Property("Image") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("IsAddWeight") .HasColumnType("boolean"); @@ -1086,7 +1086,7 @@ namespace Atomx.Data.Migrations b.Property("Note") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("Operator") .HasColumnType("bigint"); @@ -1170,7 +1170,7 @@ namespace Atomx.Data.Migrations b.Property("Description") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("Enabled") .HasColumnType("boolean"); diff --git a/Atomx.Data/Migrations/20251203190956_0.1.cs b/Atomx.Data/Migrations/20251214094758_0.1.cs similarity index 98% rename from Atomx.Data/Migrations/20251203190956_0.1.cs rename to Atomx.Data/Migrations/20251214094758_0.1.cs index bdd4d7c..e80751c 100644 --- a/Atomx.Data/Migrations/20251203190956_0.1.cs +++ b/Atomx.Data/Migrations/20251214094758_0.1.cs @@ -19,9 +19,9 @@ namespace Atomx.Data.Migrations Id = table.Column(type: "bigint", nullable: false), UserId = table.Column(type: "bigint", nullable: false), Name = table.Column(type: "varchar(128)", nullable: false), - Email = table.Column(type: "varchar(255)", nullable: false), + Email = table.Column(type: "varchar(256)", nullable: false), Phone = table.Column(type: "varchar(20)", nullable: false), - Company = table.Column(type: "varchar(255)", nullable: false), + Company = table.Column(type: "varchar(256)", nullable: false), CountryId = table.Column(type: "bigint", nullable: false), Country = table.Column(type: "varchar(50)", nullable: false), ProvinceId = table.Column(type: "bigint", nullable: false), @@ -102,7 +102,7 @@ namespace Atomx.Data.Migrations { Id = table.Column(type: "bigint", nullable: false), ParentId = table.Column(type: "bigint", nullable: false), - Name = table.Column(type: "varchar(255)", nullable: false), + Name = table.Column(type: "varchar(256)", nullable: false), Initial = table.Column(type: "varchar(1)", nullable: false), TwoLetterISOCode = table.Column(type: "varchar(2)", nullable: false), ThreeLetterISOCode = table.Column(type: "varchar(3)", nullable: false), @@ -126,11 +126,11 @@ namespace Atomx.Data.Migrations ParentId = table.Column(type: "bigint", nullable: false), Name = table.Column(type: "varchar(25)", nullable: false), Slug = table.Column(type: "varchar(50)", nullable: false), - MetaDescription = table.Column(type: "varchar(255)", nullable: false), - MetaKeywords = table.Column(type: "varchar(255)", nullable: false), - FilterAttributes = table.Column(type: "varchar(255)", nullable: false), - Image = table.Column(type: "varchar(255)", nullable: false), - Banner = table.Column(type: "varchar(255)", nullable: false), + MetaDescription = table.Column(type: "varchar(256)", nullable: false), + MetaKeywords = table.Column(type: "varchar(256)", nullable: false), + FilterAttributes = table.Column(type: "varchar(256)", nullable: false), + Image = table.Column(type: "varchar(256)", nullable: false), + Banner = table.Column(type: "varchar(256)", nullable: false), IsNode = table.Column(type: "boolean", nullable: false), Enabled = table.Column(type: "boolean", nullable: false), Depth = table.Column(type: "integer", nullable: false), @@ -200,10 +200,10 @@ namespace Atomx.Data.Migrations Name = table.Column(type: "varchar(50)", nullable: false), Title = table.Column(type: "varchar(50)", nullable: false), Culture = table.Column(type: "varchar(25)", nullable: false), - FlagImage = table.Column(type: "varchar(255)", nullable: false), + FlagImage = table.Column(type: "varchar(256)", nullable: false), DisplayOrder = table.Column(type: "integer", nullable: false), Enabled = table.Column(type: "boolean", nullable: false), - ResourceVersion = table.Column(type: "varchar(25)", nullable: false), + ResourceVersion = table.Column(type: "varchar(256)", nullable: false), CreateTime = table.Column(type: "timestamptz", nullable: false), UpdateTime = table.Column(type: "timestamptz", nullable: true) }, @@ -218,7 +218,7 @@ namespace Atomx.Data.Migrations { Id = table.Column(type: "bigint", nullable: false), LanguageId = table.Column(type: "integer", nullable: false), - Name = table.Column(type: "varchar(255)", nullable: false), + Name = table.Column(type: "varchar(256)", nullable: false), Value = table.Column(type: "varchar(1000)", nullable: false), UpdateTime = table.Column(type: "timestamptz", nullable: true) }, @@ -235,7 +235,7 @@ namespace Atomx.Data.Migrations LanguageNumber = table.Column(type: "integer", nullable: false), Type = table.Column(type: "integer", nullable: false), EntityId = table.Column(type: "bigint", nullable: false), - Key = table.Column(type: "varchar(255)", nullable: false), + Key = table.Column(type: "varchar(256)", nullable: false), Value = table.Column(type: "text", nullable: false) }, constraints: table => @@ -251,9 +251,9 @@ namespace Atomx.Data.Migrations Type = table.Column(type: "integer", nullable: false), ParentId = table.Column(type: "bigint", nullable: false), Name = table.Column(type: "varchar(50)", nullable: false), - Icon = table.Column(type: "varchar(255)", nullable: false), + Icon = table.Column(type: "varchar(256)", nullable: false), Key = table.Column(type: "varchar(50)", nullable: false), - Url = table.Column(type: "varchar(255)", nullable: false), + Url = table.Column(type: "varchar(256)", nullable: false), Code = table.Column(type: "varchar(50)", nullable: false), IsLink = table.Column(type: "boolean", nullable: false), Enabled = table.Column(type: "boolean", nullable: false), @@ -297,7 +297,7 @@ namespace Atomx.Data.Migrations Name = table.Column(type: "varchar(20)", nullable: false), Title = table.Column(type: "varchar(20)", nullable: false), Description = table.Column(type: "varchar(512)", nullable: false), - Account = table.Column(type: "varchar(255)", nullable: false), + Account = table.Column(type: "varchar(256)", nullable: false), Config = table.Column(type: "text", nullable: false), DisplayOrder = table.Column(type: "integer", nullable: false), Status = table.Column(type: "integer", nullable: false), @@ -315,7 +315,7 @@ namespace Atomx.Data.Migrations { Id = table.Column(type: "bigint", nullable: false), Name = table.Column(type: "varchar(128)", nullable: false), - Description = table.Column(type: "varchar(255)", nullable: false), + Description = table.Column(type: "varchar(256)", nullable: false), Category = table.Column(type: "varchar(128)", nullable: false) }, constraints: table => @@ -344,7 +344,7 @@ namespace Atomx.Data.Migrations MarketPrice = table.Column(type: "numeric(18,4)", nullable: false), Price = table.Column(type: "numeric(18,4)", nullable: false), Mark = table.Column(type: "integer", nullable: false), - Note = table.Column(type: "varchar(255)", nullable: false), + Note = table.Column(type: "varchar(256)", nullable: false), Enabled = table.Column(type: "boolean", nullable: false), UpdateTime = table.Column(type: "timestamptz", nullable: true) }, @@ -419,7 +419,7 @@ namespace Atomx.Data.Migrations OptionId = table.Column(type: "bigint", nullable: false), ProductAttributeRelationId = table.Column(type: "bigint", nullable: false), Name = table.Column(type: "varchar(50)", nullable: false), - Image = table.Column(type: "varchar(255)", nullable: false), + Image = table.Column(type: "varchar(256)", nullable: false), Weight = table.Column(type: "numeric(18,4)", nullable: false), WeightUnit = table.Column(type: "integer", nullable: false), IsAddWeight = table.Column(type: "boolean", nullable: false), @@ -466,7 +466,7 @@ namespace Atomx.Data.Migrations Weight = table.Column(type: "numeric(18,4)", nullable: false), BeforeStock = table.Column(type: "integer", nullable: false), AfterStock = table.Column(type: "integer", nullable: false), - Note = table.Column(type: "varchar(255)", nullable: false), + Note = table.Column(type: "varchar(256)", nullable: false), CreateTime = table.Column(type: "timestamptz", nullable: false) }, constraints: table => @@ -486,12 +486,12 @@ namespace Atomx.Data.Migrations CategoryPath = table.Column(type: "varchar(200)", nullable: true), CategoryId = table.Column(type: "bigint", nullable: false), ManufacturerId = table.Column(type: "bigint", nullable: false), - Title = table.Column(type: "varchar(255)", nullable: false), + Title = table.Column(type: "varchar(256)", nullable: false), SIN = table.Column(type: "varchar(20)", nullable: true), Image = table.Column(type: "varchar(256)", nullable: true), Photos = table.Column(type: "text", nullable: true), - Feature = table.Column(type: "varchar(255)", nullable: true), - Description = table.Column(type: "varchar(255)", nullable: true), + Feature = table.Column(type: "varchar(256)", nullable: true), + Description = table.Column(type: "varchar(256)", nullable: true), SpecificationJson = table.Column(type: "text", nullable: true), Weight = table.Column(type: "numeric(6,2)", nullable: true), MinWeight = table.Column(type: "numeric(6,2)", nullable: true), @@ -544,7 +544,7 @@ namespace Atomx.Data.Migrations .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), Type = table.Column(type: "integer", nullable: false), Name = table.Column(type: "text", nullable: false), - Description = table.Column(type: "varchar(255)", nullable: false), + Description = table.Column(type: "varchar(256)", nullable: false), Permission = table.Column(type: "text", nullable: false), IsSystemRole = table.Column(type: "boolean", nullable: false), Count = table.Column(type: "integer", nullable: false), diff --git a/Atomx.Data/Migrations/DataContextModelSnapshot.cs b/Atomx.Data/Migrations/DataContextModelSnapshot.cs index 01d7ee3..78a3d8f 100644 --- a/Atomx.Data/Migrations/DataContextModelSnapshot.cs +++ b/Atomx.Data/Migrations/DataContextModelSnapshot.cs @@ -17,7 +17,7 @@ namespace Atomx.Data.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "10.0.0") + .HasAnnotation("ProductVersion", "10.0.1") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -40,7 +40,7 @@ namespace Atomx.Data.Migrations b.Property("Company") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("Count") .HasColumnType("integer"); @@ -57,7 +57,7 @@ namespace Atomx.Data.Migrations b.Property("Email") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("FullAddress") .IsRequired() @@ -251,7 +251,7 @@ namespace Atomx.Data.Migrations b.Property("Name") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("NumericISOCode") .HasColumnType("integer"); @@ -279,7 +279,7 @@ namespace Atomx.Data.Migrations b.Property("Banner") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("Count") .HasColumnType("integer"); @@ -298,22 +298,22 @@ namespace Atomx.Data.Migrations b.Property("FilterAttributes") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("Image") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("IsNode") .HasColumnType("boolean"); b.Property("MetaDescription") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("MetaKeywords") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("Name") .IsRequired() @@ -467,7 +467,7 @@ namespace Atomx.Data.Migrations b.Property("FlagImage") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("Name") .IsRequired() @@ -475,7 +475,7 @@ namespace Atomx.Data.Migrations b.Property("ResourceVersion") .IsRequired() - .HasColumnType("varchar(25)"); + .HasColumnType("varchar(256)"); b.Property("Title") .IsRequired() @@ -499,7 +499,7 @@ namespace Atomx.Data.Migrations b.Property("Name") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("UpdateTime") .HasColumnType("timestamptz"); @@ -523,7 +523,7 @@ namespace Atomx.Data.Migrations b.Property("Key") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("LanguageNumber") .HasColumnType("integer"); @@ -563,7 +563,7 @@ namespace Atomx.Data.Migrations b.Property("Icon") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("IsLink") .HasColumnType("boolean"); @@ -591,7 +591,7 @@ namespace Atomx.Data.Migrations b.Property("Url") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.HasKey("Id"); @@ -643,7 +643,7 @@ namespace Atomx.Data.Migrations b.Property("Account") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("Config") .IsRequired() @@ -695,7 +695,7 @@ namespace Atomx.Data.Migrations b.Property("Description") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("Name") .IsRequired() @@ -733,7 +733,7 @@ namespace Atomx.Data.Migrations .HasColumnType("timestamptz"); b.Property("Description") - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("DisplayOrder") .HasColumnType("integer"); @@ -743,7 +743,7 @@ namespace Atomx.Data.Migrations .HasColumnType("text"); b.Property("Feature") - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("Image") .HasColumnType("varchar(256)"); @@ -801,7 +801,7 @@ namespace Atomx.Data.Migrations b.Property("Title") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("UpdateTime") .HasColumnType("timestamptz"); @@ -887,7 +887,7 @@ namespace Atomx.Data.Migrations b.Property("Note") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("Price") .HasColumnType("decimal(18,4)"); @@ -998,7 +998,7 @@ namespace Atomx.Data.Migrations b.Property("Image") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("IsAddWeight") .HasColumnType("boolean"); @@ -1083,7 +1083,7 @@ namespace Atomx.Data.Migrations b.Property("Note") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("Operator") .HasColumnType("bigint"); @@ -1167,7 +1167,7 @@ namespace Atomx.Data.Migrations b.Property("Description") .IsRequired() - .HasColumnType("varchar(255)"); + .HasColumnType("varchar(256)"); b.Property("Enabled") .HasColumnType("boolean");