|
|
using Microsoft.Extensions.Configuration;
|
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
using Microsoft.Extensions.Hosting;
|
|
|
using SlnMesnac.Config;
|
|
|
using SlnMesnac.Quartz;
|
|
|
using TouchSocket.Sockets;
|
|
|
using SlnMesnac.Extensions;
|
|
|
using Microsoft.AspNetCore.Hosting;
|
|
|
using Serilog;
|
|
|
using Autofac.Extensions.DependencyInjection;
|
|
|
using SlnMesnac.Serilog;
|
|
|
|
|
|
namespace ConsoleApp
|
|
|
{
|
|
|
internal class Program
|
|
|
{
|
|
|
private static SerilogHelper? serilogHelper;
|
|
|
private static IHost? host;
|
|
|
|
|
|
static async Task Main(string[] args)
|
|
|
{
|
|
|
// 解析实例ID - 支持多种参数格式
|
|
|
var instanceId = GetInstanceId(args);
|
|
|
Console.Title = $"iMES System - Instance {instanceId}";
|
|
|
|
|
|
// 设置环境变量,确保实例完全隔离
|
|
|
Environment.SetEnvironmentVariable("INSTANCE_ID", instanceId);
|
|
|
Environment.SetEnvironmentVariable("DOTNET_ENVIRONMENT", $"Instance{instanceId}");
|
|
|
|
|
|
try
|
|
|
{
|
|
|
Thread.CurrentThread.Name = $"Main-{instanceId}";
|
|
|
Console.ForegroundColor = GetConsoleColor(instanceId);
|
|
|
|
|
|
AppDomain.CurrentDomain.UnhandledException += (sender, e) =>
|
|
|
GlobalExceptionHandler(sender, e, instanceId);
|
|
|
|
|
|
// 创建完全独立的Host构建器
|
|
|
host = CreateHostBuilder(args, instanceId).Build();
|
|
|
|
|
|
// 预先验证服务
|
|
|
using (var scope = host.Services.CreateScope())
|
|
|
{
|
|
|
var services = scope.ServiceProvider;
|
|
|
try
|
|
|
{
|
|
|
|
|
|
serilogHelper = services.GetRequiredService<SerilogHelper>();
|
|
|
serilogHelper.Info($"启动服务");
|
|
|
var appConfig = services.GetRequiredService<AppConfig>();
|
|
|
|
|
|
// 强制设置实例ID到配置对象
|
|
|
SetInstanceSpecificConfiguration(appConfig, instanceId);
|
|
|
|
|
|
serilogHelper.Info($"实例 {instanceId} 服务初始化完成");
|
|
|
Console.WriteLine($"实例 {instanceId} 服务初始化完成");
|
|
|
}
|
|
|
catch (Exception ex)
|
|
|
{
|
|
|
Console.WriteLine($"实例 {instanceId} 服务初始化失败: {ex.Message}");
|
|
|
serilogHelper.Info($"详细错误: {ex}");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 启动host
|
|
|
serilogHelper.Info($"正在启动实例 {instanceId} 的Host...");
|
|
|
Console.WriteLine($"正在启动实例 {instanceId} 的Host...");
|
|
|
var hostTask = host.RunAsync();
|
|
|
|
|
|
// 等待host完全启动
|
|
|
await Task.Delay(2000);
|
|
|
|
|
|
// 启动主控制逻辑
|
|
|
Console.WriteLine($"正在启动实例 {instanceId} 的主控制逻辑...");
|
|
|
MainCentralControl mainCentralControl = new MainCentralControl(host);
|
|
|
mainCentralControl.Start();
|
|
|
|
|
|
serilogHelper?.Info($"iMES系统实例 {instanceId} 启动成功");
|
|
|
Console.WriteLine($"实例 {instanceId} 已成功启动!");
|
|
|
Console.WriteLine("按 Q 退出程序...");
|
|
|
|
|
|
// 保持程序运行
|
|
|
while (true)
|
|
|
{
|
|
|
var key = Console.ReadKey(true);
|
|
|
if (key.Key == ConsoleKey.Q)
|
|
|
{
|
|
|
Console.WriteLine($"正在停止实例 {instanceId}...");
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
catch (Exception ex)
|
|
|
{
|
|
|
Console.WriteLine($"实例 {instanceId} 启动失败: {ex.Message}");
|
|
|
Console.WriteLine($"堆栈跟踪: {ex.StackTrace}");
|
|
|
serilogHelper?.Info($"实例 {instanceId} 启动失败: {ex.Message}\n{ex.StackTrace}");
|
|
|
|
|
|
if (ex.InnerException != null)
|
|
|
{
|
|
|
Console.WriteLine($"内部异常: {ex.InnerException.Message}");
|
|
|
serilogHelper?.Info($"内部异常: {ex.InnerException.Message}");
|
|
|
}
|
|
|
}
|
|
|
finally
|
|
|
{
|
|
|
Console.WriteLine($"实例 {instanceId} 清理资源...");
|
|
|
if (host is IAsyncDisposable asyncDisposable)
|
|
|
{
|
|
|
await asyncDisposable.DisposeAsync();
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
host?.Dispose();
|
|
|
}
|
|
|
Console.WriteLine($"实例 {instanceId} 已退出");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private static string GetInstanceId(string[] args)
|
|
|
{
|
|
|
// 支持多种参数格式
|
|
|
foreach (var arg in args)
|
|
|
{
|
|
|
if (arg.StartsWith("--instance"))
|
|
|
{
|
|
|
if (arg == "--instance")
|
|
|
{
|
|
|
// 查找下一个参数作为实例ID
|
|
|
var index = Array.IndexOf(args, arg);
|
|
|
if (index + 1 < args.Length)
|
|
|
{
|
|
|
return args[index + 1];
|
|
|
}
|
|
|
}
|
|
|
else if (arg.StartsWith("--instance="))
|
|
|
{
|
|
|
return arg.Substring("--instance=".Length);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
// 格式: --instance1, --instance2
|
|
|
return arg.Substring("--instance".Length);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 如果没有指定实例ID,使用随机ID避免冲突
|
|
|
Random random = new Random();
|
|
|
return random.Next(0000, 9999).ToString();
|
|
|
}
|
|
|
|
|
|
private static ConsoleColor GetConsoleColor(string instanceId)
|
|
|
{
|
|
|
// 为不同实例分配不同颜色,便于区分
|
|
|
return instanceId switch
|
|
|
{
|
|
|
"1" => ConsoleColor.Green,
|
|
|
"2" => ConsoleColor.Cyan,
|
|
|
"3" => ConsoleColor.Magenta,
|
|
|
"4" => ConsoleColor.Yellow,
|
|
|
_ => ConsoleColor.White
|
|
|
};
|
|
|
}
|
|
|
|
|
|
private static void SetInstanceSpecificConfiguration(AppConfig appConfig, string instanceId)
|
|
|
{
|
|
|
try
|
|
|
{
|
|
|
Console.WriteLine($"为实例 {instanceId} 设置特定配置...");
|
|
|
|
|
|
// 使用反射设置所有可能的配置属性
|
|
|
var configType = appConfig.GetType();
|
|
|
var properties = configType.GetProperties();
|
|
|
|
|
|
foreach (var property in properties)
|
|
|
{
|
|
|
if (property.CanWrite)
|
|
|
{
|
|
|
var value = property.GetValue(appConfig);
|
|
|
if (value is string stringValue)
|
|
|
{
|
|
|
// 替换路径中的占位符
|
|
|
if (stringValue.Contains("{Instance}") ||
|
|
|
stringValue.Contains("{instance}"))
|
|
|
{
|
|
|
var newValue = stringValue
|
|
|
.Replace("{Instance}", instanceId)
|
|
|
.Replace("{instance}", instanceId);
|
|
|
property.SetValue(appConfig, newValue);
|
|
|
Console.WriteLine($" 更新 {property.Name}: {newValue}");
|
|
|
}
|
|
|
else if (IsPathProperty(property.Name) &&
|
|
|
!stringValue.Contains(instanceId))
|
|
|
{
|
|
|
// 为路径属性添加实例ID
|
|
|
var newValue = $"{stringValue}_Instance{instanceId}";
|
|
|
property.SetValue(appConfig, newValue);
|
|
|
Console.WriteLine($" 更新 {property.Name}: {newValue}");
|
|
|
}
|
|
|
}
|
|
|
else if (property.PropertyType == typeof(int) &&
|
|
|
IsPortProperty(property.Name))
|
|
|
{
|
|
|
// 调整端口号
|
|
|
var basePort = (int)value;
|
|
|
var newPort = basePort + (int.Parse(instanceId) - 1) * 10;
|
|
|
property.SetValue(appConfig, newPort);
|
|
|
Console.WriteLine($" 更新 {property.Name}: {newPort}");
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
catch (Exception ex)
|
|
|
{
|
|
|
Console.WriteLine($"设置实例配置时出错: {ex.Message}");
|
|
|
serilogHelper?.Info($"设置实例配置时出错: {ex.Message}");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private static bool IsPathProperty(string propertyName)
|
|
|
{
|
|
|
return propertyName.ToLower().Contains("path") ||
|
|
|
propertyName.ToLower().Contains("dir") ||
|
|
|
propertyName.ToLower().Contains("file");
|
|
|
}
|
|
|
|
|
|
private static bool IsPortProperty(string propertyName)
|
|
|
{
|
|
|
return propertyName.ToLower().Contains("port");
|
|
|
}
|
|
|
|
|
|
private static void GlobalExceptionHandler(object sender, UnhandledExceptionEventArgs e, string instanceId)
|
|
|
{
|
|
|
Thread.CurrentThread.Name = $"Error-{instanceId}";
|
|
|
Exception? exception = e.ExceptionObject as Exception;
|
|
|
|
|
|
string errorMessage = exception != null
|
|
|
? $"实例 {instanceId} 全局异常捕获:\n{exception.Message}\n{exception.StackTrace}"
|
|
|
: $"实例 {instanceId} 未知异常捕获:\n{e.ExceptionObject}";
|
|
|
|
|
|
Console.WriteLine(errorMessage);
|
|
|
serilogHelper?.Error(errorMessage);
|
|
|
}
|
|
|
|
|
|
public static IHostBuilder CreateHostBuilder(string[] args, string instanceId) =>
|
|
|
Host.CreateDefaultBuilder(args)
|
|
|
.UseSerilog((context, config) =>
|
|
|
{
|
|
|
// 为每个实例配置独立的Serilog
|
|
|
config.WriteTo.Console(
|
|
|
outputTemplate: $"[{{Timestamp:HH:mm:ss}} {{Level:u3}}] Instance{instanceId}: {{Message:lj}}{{NewLine}}{{Exception}}")
|
|
|
.WriteTo.File(
|
|
|
path: $"./Logs/Instance{instanceId}/log-.txt",
|
|
|
rollingInterval: RollingInterval.Day,
|
|
|
outputTemplate: $"[{{Timestamp:yyyy-MM-dd HH:mm:ss}} {{Level:u3}}] Instance{instanceId}: {{Message:lj}}{{NewLine}}{{Exception}}");
|
|
|
})
|
|
|
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
|
|
|
.ConfigureAppConfiguration((context, config) =>
|
|
|
{
|
|
|
// 清除默认配置,从头开始构建
|
|
|
config.Sources.Clear();
|
|
|
|
|
|
// 按优先级添加配置源
|
|
|
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
|
|
|
.AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json", optional: true, reloadOnChange: true)
|
|
|
.AddJsonFile($"appsettings.instance{instanceId}.json", optional: true, reloadOnChange: true)
|
|
|
.AddEnvironmentVariables("INSTANCE_")
|
|
|
.AddCommandLine(args);
|
|
|
})
|
|
|
.ConfigureWebHostDefaults(webBuilder =>
|
|
|
{
|
|
|
webBuilder.UseStartup<Startup>();
|
|
|
|
|
|
webBuilder.ConfigureKestrel(serverOptions =>
|
|
|
{
|
|
|
Console.WriteLine(int.Parse(instanceId));
|
|
|
|
|
|
serverOptions.ListenAnyIP(1 + (int.Parse(instanceId) - 1));
|
|
|
});
|
|
|
})
|
|
|
.ConfigureServices((context, services) =>
|
|
|
{
|
|
|
services.AddSingleton(new InstanceInfo { Id = instanceId });
|
|
|
});
|
|
|
}
|
|
|
|
|
|
public class InstanceInfo
|
|
|
{
|
|
|
public string Id { get; set; } = "1";
|
|
|
|
|
|
}
|
|
|
}
|