refactor(config): 将 appsettings.json 转为嵌入资源
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user