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>
|
<LangVersion>12.0</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<InternalsVisibleTo Include="Gpulse.WCT.DataAnalyzer.Tests" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<!-- CLI -->
|
<!-- CLI -->
|
||||||
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
|
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
|
||||||
@@ -36,9 +40,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Update="appsettings.json">
|
<EmbeddedResource Include="appsettings.json" />
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using System.CommandLine;
|
using System.CommandLine;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text.Json;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
@@ -97,8 +99,16 @@ public class Program
|
|||||||
{
|
{
|
||||||
var builder = new ConfigurationBuilder()
|
var builder = new ConfigurationBuilder()
|
||||||
.SetBasePath(AppContext.BaseDirectory)
|
.SetBasePath(AppContext.BaseDirectory)
|
||||||
.AddJsonFile("appsettings.json", optional: false)
|
.AddEmbeddedResourceJson(
|
||||||
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? "Production"}.json", optional: true)
|
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()
|
.AddEnvironmentVariables()
|
||||||
.AddCommandLine(args);
|
.AddCommandLine(args);
|
||||||
|
|
||||||
@@ -163,3 +173,84 @@ public class Program
|
|||||||
: Path.GetFullPath(Path.Combine(baseDirectory, value));
|
: 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.Extensions;
|
||||||
using Gpulse.WCT.DataAnalyzer.Core.Infrastructure.LocalData;
|
using Gpulse.WCT.DataAnalyzer.Core.Infrastructure.LocalData;
|
||||||
using Microsoft.Data.Sqlite;
|
using Microsoft.Data.Sqlite;
|
||||||
@@ -56,7 +57,11 @@ public sealed class CliTestHost : IDisposable
|
|||||||
var releaseConnection = new SqliteConnection("Data Source=:memory:");
|
var releaseConnection = new SqliteConnection("Data Source=:memory:");
|
||||||
await releaseConnection.OpenAsync();
|
await releaseConnection.OpenAsync();
|
||||||
|
|
||||||
|
// 从嵌入资源加载 appsettings.json,与 Program.Main 生产路径一致
|
||||||
var configuration = new ConfigurationBuilder()
|
var configuration = new ConfigurationBuilder()
|
||||||
|
.AddEmbeddedResourceJson(
|
||||||
|
typeof(Program).Assembly,
|
||||||
|
"Gpulse.WCT.DataAnalyzer.appsettings.json")
|
||||||
.AddInMemoryCollection(new Dictionary<string, string?>
|
.AddInMemoryCollection(new Dictionary<string, string?>
|
||||||
{
|
{
|
||||||
["Database:Type"] = "sqlite",
|
["Database:Type"] = "sqlite",
|
||||||
|
|||||||
Reference in New Issue
Block a user