diff --git a/src/Gpulse.WCT.DataAnalyzer/Program.cs b/src/Gpulse.WCT.DataAnalyzer/Program.cs index dbe6a28..fa8f20b 100644 --- a/src/Gpulse.WCT.DataAnalyzer/Program.cs +++ b/src/Gpulse.WCT.DataAnalyzer/Program.cs @@ -17,14 +17,8 @@ public class Program { public static async Task 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(); } } + + /// + /// 构建配置。除支持 json/环境变量/命令行外, + /// 将 appsettings.json 中数据库、日志、输出等相对路径统一解析为基于 exe 所在目录的绝对路径, + /// 保证应用从任意工作目录启动时,配置文件、数据库、日志都落在 exe 旁边。 + /// + 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(); + } + + /// + /// 将配置中的相对路径解析为基于 exe 目录的绝对路径。 + /// + private static Dictionary ResolveRelativePaths(IConfiguration config) + { + var baseDirectory = AppContext.BaseDirectory; + var overrides = new Dictionary(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 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)); + } }