refactor(config): 将 appsettings.json 转为嵌入资源

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Scottxjw
2026-08-17 10:47:00 +08:00
parent 759da546ee
commit 36c21f03dd
3 changed files with 103 additions and 5 deletions

View File

@@ -10,6 +10,10 @@
<LangVersion>12.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<InternalsVisibleTo Include="Gpulse.WCT.DataAnalyzer.Tests" />
</ItemGroup>
<ItemGroup>
<!-- CLI -->
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
@@ -36,9 +40,7 @@
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<EmbeddedResource Include="appsettings.json" />
</ItemGroup>
</Project>

View File

@@ -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<string, string?> FlattenJson(JsonElement element)
{
var result = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
Walk(element, "", result);
return result;
static void Walk(JsonElement el, string prefix, Dictionary<string, string?> 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;
}
}

View File

@@ -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<string, string?>
{
["Database:Type"] = "sqlite",