feat(cli): 配置相对路径按 exe 目录解析
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -17,14 +17,8 @@ public class Program
|
|||||||
{
|
{
|
||||||
public static async Task<int> Main(string[] args)
|
public static async Task<int> Main(string[] args)
|
||||||
{
|
{
|
||||||
// 1. 构建引导配置(用于 Serilog 初始化)
|
// 1. 构建引导配置(用于 Serilog 初始化),并将数据库/日志等相对路径解析到 exe 所在目录
|
||||||
var bootstrapConfig = new ConfigurationBuilder()
|
var bootstrapConfig = BuildConfiguration(args);
|
||||||
.SetBasePath(AppContext.BaseDirectory)
|
|
||||||
.AddJsonFile("appsettings.json", optional: false)
|
|
||||||
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? "Production"}.json", optional: true)
|
|
||||||
.AddEnvironmentVariables()
|
|
||||||
.AddCommandLine(args)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
// 2. 创建引导日志器
|
// 2. 创建引导日志器
|
||||||
Log.Logger = new LoggerConfiguration()
|
Log.Logger = new LoggerConfiguration()
|
||||||
@@ -89,4 +83,79 @@ public class Program
|
|||||||
await Log.CloseAndFlushAsync();
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user