From 36c21f03dd4d253cc261ae9e631d201288a01479 Mon Sep 17 00:00:00 2001 From: Scottxjw <13374147+scottxjw@user.noreply.gitee.com> Date: Mon, 17 Aug 2026 10:47:00 +0800 Subject: [PATCH] =?UTF-8?q?refactor(config):=20=E5=B0=86=20appsettings.jso?= =?UTF-8?q?n=20=E8=BD=AC=E4=B8=BA=E5=B5=8C=E5=85=A5=E8=B5=84=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../Gpulse.WCT.DataAnalyzer.csproj | 8 +- src/Gpulse.WCT.DataAnalyzer/Program.cs | 95 ++++++++++++++++++- .../CliTestHost.cs | 5 + 3 files changed, 103 insertions(+), 5 deletions(-) diff --git a/src/Gpulse.WCT.DataAnalyzer/Gpulse.WCT.DataAnalyzer.csproj b/src/Gpulse.WCT.DataAnalyzer/Gpulse.WCT.DataAnalyzer.csproj index 214a0a0..891844e 100644 --- a/src/Gpulse.WCT.DataAnalyzer/Gpulse.WCT.DataAnalyzer.csproj +++ b/src/Gpulse.WCT.DataAnalyzer/Gpulse.WCT.DataAnalyzer.csproj @@ -10,6 +10,10 @@ 12.0 + + + + @@ -36,9 +40,7 @@ - - PreserveNewest - + diff --git a/src/Gpulse.WCT.DataAnalyzer/Program.cs b/src/Gpulse.WCT.DataAnalyzer/Program.cs index e8e16c8..39d37e3 100644 --- a/src/Gpulse.WCT.DataAnalyzer/Program.cs +++ b/src/Gpulse.WCT.DataAnalyzer/Program.cs @@ -1,4 +1,6 @@ using System.CommandLine; +using System.Reflection; +using System.Text.Json; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -97,8 +99,16 @@ public class Program { var builder = new ConfigurationBuilder() .SetBasePath(AppContext.BaseDirectory) - .AddJsonFile("appsettings.json", optional: false) - .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? "Production"}.json", optional: true) + .AddEmbeddedResourceJson( + Assembly.GetExecutingAssembly(), + "Gpulse.WCT.DataAnalyzer.appsettings.json") + .AddJsonFile( + Path.Combine(AppContext.BaseDirectory, "appsettings.json"), + optional: true, reloadOnChange: false) + .AddJsonFile( + Path.Combine(AppContext.BaseDirectory, + $"appsettings.{Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? "Production"}.json"), + optional: true, reloadOnChange: false) .AddEnvironmentVariables() .AddCommandLine(args); @@ -163,3 +173,84 @@ public class Program : Path.GetFullPath(Path.Combine(baseDirectory, value)); } } + +// ── 嵌入资源配置加载 ────────────────────────────────────────────────────────── + +internal sealed class EmbeddedResourceJsonConfigurationProvider( + EmbeddedResourceJsonConfigurationSource source) : FileConfigurationProvider(source) +{ + public override void Load(Stream stream) + { + using var doc = JsonDocument.Parse(stream); + Data = FlattenJson(doc.RootElement); + } + + public override void Load() + { + var assembly = source.Assembly; + var resourceName = source.ResourceName; + + using var stream = assembly.GetManifestResourceStream(resourceName) + ?? throw new InvalidOperationException( + $"嵌入资源 '{resourceName}' 不存在。" + + $" 可用资源: [{string.Join(", ", assembly.GetManifestResourceNames())}]"); + + Load(stream); + } + + private static Dictionary FlattenJson(JsonElement element) + { + var result = new Dictionary(StringComparer.OrdinalIgnoreCase); + Walk(element, "", result); + return result; + + static void Walk(JsonElement el, string prefix, Dictionary bag) + { + switch (el.ValueKind) + { + case JsonValueKind.Object: + foreach (var prop in el.EnumerateObject()) + Walk(prop.Value, string.IsNullOrEmpty(prefix) ? prop.Name : $"{prefix}:{prop.Name}", bag); + break; + case JsonValueKind.Array: + var i = 0; + foreach (var item in el.EnumerateArray()) + { + Walk(item, $"{prefix}:{i}", bag); + i++; + } + break; + default: + bag[prefix] = el.ToString(); + break; + } + } + } +} + +internal sealed class EmbeddedResourceJsonConfigurationSource : FileConfigurationSource +{ + public Assembly Assembly { get; } = null!; + public string ResourceName { get; } = null!; + + public EmbeddedResourceJsonConfigurationSource(Assembly assembly, string resourceName) + { + Assembly = assembly; + ResourceName = resourceName; + } + + public override IConfigurationProvider Build(IConfigurationBuilder builder) => + new EmbeddedResourceJsonConfigurationProvider(this); +} + +public static class EmbeddedResourceConfigurationExtensions +{ + public static IConfigurationBuilder AddEmbeddedResourceJson( + this IConfigurationBuilder builder, + Assembly assembly, + string resourceName) + { + builder.Add(new EmbeddedResourceJsonConfigurationSource(assembly, resourceName)); + return builder; + } +} diff --git a/tests/Gpulse.WCT.DataAnalyzer.Tests/CliTestHost.cs b/tests/Gpulse.WCT.DataAnalyzer.Tests/CliTestHost.cs index 219a6df..c97ce4d 100644 --- a/tests/Gpulse.WCT.DataAnalyzer.Tests/CliTestHost.cs +++ b/tests/Gpulse.WCT.DataAnalyzer.Tests/CliTestHost.cs @@ -1,3 +1,4 @@ +using System.Reflection; using Gpulse.WCT.DataAnalyzer.Core.Infrastructure.Extensions; using Gpulse.WCT.DataAnalyzer.Core.Infrastructure.LocalData; using Microsoft.Data.Sqlite; @@ -56,7 +57,11 @@ public sealed class CliTestHost : IDisposable var releaseConnection = new SqliteConnection("Data Source=:memory:"); await releaseConnection.OpenAsync(); + // 从嵌入资源加载 appsettings.json,与 Program.Main 生产路径一致 var configuration = new ConfigurationBuilder() + .AddEmbeddedResourceJson( + typeof(Program).Assembly, + "Gpulse.WCT.DataAnalyzer.appsettings.json") .AddInMemoryCollection(new Dictionary { ["Database:Type"] = "sqlite",