feat(cli): 配置相对路径按 exe 目录解析

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Scottxjw
2026-08-14 09:24:12 +08:00
parent f3472d21ee
commit 77d4bfe953

View File

@@ -17,14 +17,8 @@ public class Program
{
public static async Task<int> Main(string[] args)
{
// 1. 构建引导配置(用于 Serilog 初始化)
var bootstrapConfig = new ConfigurationBuilder()
.SetBasePath(AppContext.BaseDirectory)
.AddJsonFile("appsettings.json", optional: false)
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? "Production"}.json", optional: true)
.AddEnvironmentVariables()
.AddCommandLine(args)
.Build();
// 1. 构建引导配置(用于 Serilog 初始化),并将数据库/日志等相对路径解析到 exe 所在目录
var bootstrapConfig = BuildConfiguration(args);
// 2. 创建引导日志器
Log.Logger = new LoggerConfiguration()
@@ -89,4 +83,79 @@ public class Program
await Log.CloseAndFlushAsync();
}
}
/// <summary>
/// 构建配置。除支持 json/环境变量/命令行外,
/// 将 appsettings.json 中数据库、日志、输出等相对路径统一解析为基于 exe 所在目录的绝对路径,
/// 保证应用从任意工作目录启动时,配置文件、数据库、日志都落在 exe 旁边。
/// </summary>
private static IConfiguration BuildConfiguration(string[] args)
{
var builder = new ConfigurationBuilder()
.SetBasePath(AppContext.BaseDirectory)
.AddJsonFile("appsettings.json", optional: false)
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? "Production"}.json", optional: true)
.AddEnvironmentVariables()
.AddCommandLine(args);
var config = builder.Build();
// 收集需要解析的相对路径,通过内存配置覆盖层注入(优先级高于 json/环境变量/命令行)
var overrides = ResolveRelativePaths(config);
if (overrides.Count == 0)
return config;
return new ConfigurationBuilder()
.AddConfiguration(config)
.AddInMemoryCollection(overrides)
.Build();
}
/// <summary>
/// 将配置中的相对路径解析为基于 exe 目录的绝对路径。
/// </summary>
private static Dictionary<string, string?> ResolveRelativePaths(IConfiguration config)
{
var baseDirectory = AppContext.BaseDirectory;
var overrides = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
// 数据库文件
foreach (var key in new[] { "Database:Path", "Database:LocalPath", "Database:ReleasePath" })
{
AddResolved(overrides, config, key, baseDirectory);
}
// 日志与输出目录
foreach (var key in new[] { "Paths:LogDir", "Paths:OutputDir" })
{
AddResolved(overrides, config, key, baseDirectory);
}
// Serilog 文件日志路径(遍历 WriteTo 数组,避免硬编码索引)
foreach (var sink in config.GetSection("Serilog:WriteTo").GetChildren())
{
if (!string.Equals(sink["Name"], "File", StringComparison.OrdinalIgnoreCase))
continue;
AddResolved(overrides, config, $"{sink.Path}:Args:path", baseDirectory);
}
return overrides;
}
private static void AddResolved(
Dictionary<string, string?> overrides,
IConfiguration config,
string key,
string baseDirectory)
{
var value = config[key];
if (string.IsNullOrWhiteSpace(value))
return;
overrides[key] = Path.IsPathRooted(value)
? value
: Path.GetFullPath(Path.Combine(baseDirectory, value));
}
}