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",