|
|
|
|
@ -1,16 +1,20 @@
|
|
|
|
|
using Autofac.Extensions.DependencyInjection;
|
|
|
|
|
using Lierda.WPFHelper;
|
|
|
|
|
using Microsoft.AspNetCore.Hosting;
|
|
|
|
|
using Microsoft.Extensions.Configuration;
|
|
|
|
|
using Lierda.WPFHelper;
|
|
|
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
|
using Microsoft.Extensions.Hosting;
|
|
|
|
|
using Serilog;
|
|
|
|
|
using SlnMesnac.Config;
|
|
|
|
|
using SlnMesnac.Plc;
|
|
|
|
|
using SlnMesnac.Rfid;
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Windows;
|
|
|
|
|
using Microsoft.Extensions.Configuration;
|
|
|
|
|
using SlnMesnac.Extensions;
|
|
|
|
|
using SlnMesnac.Serilog;
|
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Reflection;
|
|
|
|
|
using TouchSocket.Sockets;
|
|
|
|
|
using SlnMesnac.WPF.Attribute;
|
|
|
|
|
using SlnMesnac.Rfid;
|
|
|
|
|
|
|
|
|
|
namespace SlnMesnac.WPF
|
|
|
|
|
{
|
|
|
|
|
@ -19,65 +23,97 @@ namespace SlnMesnac.WPF
|
|
|
|
|
/// </summary>
|
|
|
|
|
public partial class App : Application
|
|
|
|
|
{
|
|
|
|
|
private System.Threading.Mutex? mutex = null;
|
|
|
|
|
private Mutex _instanceMutex = null;
|
|
|
|
|
private LierdaCracker cracker = new LierdaCracker();
|
|
|
|
|
public static IServiceProvider? ServiceProvider = null;
|
|
|
|
|
private static IHost? host;
|
|
|
|
|
private AppConfig appConfig;
|
|
|
|
|
public static IServiceProvider ServiceProvider { get; private set; }
|
|
|
|
|
|
|
|
|
|
// 实例唯一标识符
|
|
|
|
|
private static string _instanceId;
|
|
|
|
|
|
|
|
|
|
// 实例数据目录
|
|
|
|
|
private static string _instanceDataPath;
|
|
|
|
|
|
|
|
|
|
public new static App Current => (App)Application.Current;
|
|
|
|
|
|
|
|
|
|
// 获取当前实例ID(静态属性)
|
|
|
|
|
public static string InstanceId => _instanceId;
|
|
|
|
|
|
|
|
|
|
// 获取当前实例的数据路径
|
|
|
|
|
public static string InstanceDataPath => _instanceDataPath;
|
|
|
|
|
// Startup事件
|
|
|
|
|
protected override async void OnStartup(StartupEventArgs e)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
bool ret;
|
|
|
|
|
//mutex = new System.Threading.Mutex(true, System.Diagnostics.Process.GetCurrentProcess().ProcessName, out ret);
|
|
|
|
|
//if (!ret)
|
|
|
|
|
//{
|
|
|
|
|
// MessageBox.Show("应用程序已开启,禁止重复运行");
|
|
|
|
|
// Environment.Exit(0);
|
|
|
|
|
//}
|
|
|
|
|
this.DispatcherUnhandledException += App_DispatcherUnhandledException; //全局异常处理
|
|
|
|
|
#region 多实例初始化
|
|
|
|
|
// 从命令行参数获取实例ID(如果指定了)
|
|
|
|
|
string customInstanceId = GetInstanceIdFromArgs(e.Args);
|
|
|
|
|
|
|
|
|
|
// 生成实例ID
|
|
|
|
|
_instanceId = !string.IsNullOrEmpty(customInstanceId)
|
|
|
|
|
? customInstanceId
|
|
|
|
|
: GenerateInstanceId();
|
|
|
|
|
|
|
|
|
|
// 初始化实例数据目录
|
|
|
|
|
InitializeInstanceDataPath();
|
|
|
|
|
|
|
|
|
|
// 创建基于实例ID的互斥体名称,确保每个实例独立
|
|
|
|
|
string mutexName = $"Global\\{Process.GetCurrentProcess().ProcessName}_{_instanceId}";
|
|
|
|
|
bool createdNew;
|
|
|
|
|
_instanceMutex = new Mutex(true, mutexName, out createdNew);
|
|
|
|
|
|
|
|
|
|
if (!createdNew)
|
|
|
|
|
{
|
|
|
|
|
// 如果mutex已存在,说明相同实例ID的程序已经在运行
|
|
|
|
|
MessageBox.Show($"实例 {_instanceId} 已在运行中,无法重复启动!",
|
|
|
|
|
"启动失败", MessageBoxButton.OK, MessageBoxImage.Warning);
|
|
|
|
|
Environment.Exit(0);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
cracker.Cracker(100); //设置GC回收间隔
|
|
|
|
|
|
|
|
|
|
base.OnStartup(e);
|
|
|
|
|
//var host = CreateHostBuilder(e.Args).Build();//生成宿主。
|
|
|
|
|
|
|
|
|
|
//ServiceProvider = host.Services;
|
|
|
|
|
// 设置ServiceCollection
|
|
|
|
|
var services = new ServiceCollection();
|
|
|
|
|
ConfigureServices(services); // 配置服务
|
|
|
|
|
|
|
|
|
|
//await host.StartAsync();
|
|
|
|
|
var instanceId = GetInstanceId(e.Args);
|
|
|
|
|
// 创建完全独立的Host构建器
|
|
|
|
|
host = CreateHostBuilder(e.Args, instanceId).Build();
|
|
|
|
|
// 创建ServiceProvider
|
|
|
|
|
ServiceProvider = services.BuildServiceProvider();
|
|
|
|
|
|
|
|
|
|
// 预先验证服务
|
|
|
|
|
using (var scope = host.Services.CreateScope())
|
|
|
|
|
// 配置Serilog和其他扩展
|
|
|
|
|
ServiceProvider.UseSerilogExtensions();
|
|
|
|
|
|
|
|
|
|
// 获取AppConfig并更新实例特定的配置
|
|
|
|
|
var appConfig = ServiceProvider.GetService<AppConfig>();
|
|
|
|
|
if (appConfig != null)
|
|
|
|
|
{
|
|
|
|
|
ServiceProvider = scope.ServiceProvider;
|
|
|
|
|
// 更新日志路径为实例专用路径
|
|
|
|
|
appConfig.logPath = Path.Combine(_instanceDataPath, "Logs");
|
|
|
|
|
Directory.CreateDirectory(appConfig.logPath);
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
// 更新其他可能需要实例化的路径
|
|
|
|
|
appConfig.InstanceId = _instanceId;
|
|
|
|
|
appConfig.InstanceDataPath = _instanceDataPath;
|
|
|
|
|
|
|
|
|
|
//serilogHelper = services.GetRequiredService<SerilogHelper>();
|
|
|
|
|
//serilogHelper.Info($"启动服务");
|
|
|
|
|
//var appConfig = services.GetRequiredService<AppConfig>();
|
|
|
|
|
appConfig = host.Services.GetService<AppConfig>();
|
|
|
|
|
|
|
|
|
|
// 强制设置实例ID到配置对象
|
|
|
|
|
SetInstanceSpecificConfiguration(appConfig, instanceId);
|
|
|
|
|
|
|
|
|
|
//serilogHelper.Info($"实例 {instanceId} 服务初始化完成");
|
|
|
|
|
Console.WriteLine($"实例 {instanceId} 服务初始化完成");
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine($"实例 {instanceId} 服务初始化失败: {ex.Message}");
|
|
|
|
|
//serilogHelper.Info($"详细错误: {ex}");
|
|
|
|
|
}
|
|
|
|
|
// 根据实例ID调整数据库连接(如果需要)
|
|
|
|
|
// AdjustDatabaseConnectionForInstance(appConfig);
|
|
|
|
|
}
|
|
|
|
|
//await host.StartAsync();
|
|
|
|
|
var hostTask = host.RunAsync();
|
|
|
|
|
var logPath = $"{appConfig.logPath}/Logs/{DateTime.UtcNow:yyyy-MM-dd}/";
|
|
|
|
|
Log.Information($"系统初始化完成,日志存放路径:{appConfig.logPath}");
|
|
|
|
|
|
|
|
|
|
Log.Information($"应用程序启动 - 实例ID: {_instanceId}, 进程ID: {Process.GetCurrentProcess().Id}");
|
|
|
|
|
Log.Information($"日志目录: {appConfig?.logPath}");
|
|
|
|
|
Log.Information($"数据目录: {_instanceDataPath}");
|
|
|
|
|
|
|
|
|
|
// 显示主窗口
|
|
|
|
|
var loginWindow = ServiceProvider.GetRequiredService<MainWindow>();
|
|
|
|
|
loginWindow.WindowStartupLocation = WindowStartupLocation.CenterScreen;
|
|
|
|
|
|
|
|
|
|
// 在窗口标题显示实例信息
|
|
|
|
|
loginWindow.Title = $"{loginWindow.Title} [{_instanceId}]";
|
|
|
|
|
loginWindow.Show();
|
|
|
|
|
}
|
|
|
|
|
catch (Exception exception)
|
|
|
|
|
{
|
|
|
|
|
@ -87,170 +123,399 @@ namespace SlnMesnac.WPF
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
private static void SetInstanceSpecificConfiguration(AppConfig appConfig, string instanceId)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 从命令行参数获取实例ID
|
|
|
|
|
/// </summary>
|
|
|
|
|
private string GetInstanceIdFromArgs(string[] args)
|
|
|
|
|
{
|
|
|
|
|
if (args == null || args.Length == 0)
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
// 支持两种参数格式:/instance:ID 或 -instance=ID
|
|
|
|
|
foreach (var arg in args)
|
|
|
|
|
{
|
|
|
|
|
if (arg.StartsWith("/instance:") || arg.StartsWith("-instance="))
|
|
|
|
|
{
|
|
|
|
|
return arg.Contains(':')
|
|
|
|
|
? arg.Substring(arg.IndexOf(':') + 1)
|
|
|
|
|
: arg.Substring(arg.IndexOf('=') + 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 生成实例ID
|
|
|
|
|
/// </summary>
|
|
|
|
|
private string GenerateInstanceId()
|
|
|
|
|
{
|
|
|
|
|
string timestamp = DateTime.Now.ToString("yyyyMMddHHmmss");
|
|
|
|
|
string random = new Random().Next(1000, 9999).ToString();
|
|
|
|
|
return $"{timestamp}_{random}";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 初始化实例数据路径
|
|
|
|
|
/// </summary>
|
|
|
|
|
private void InitializeInstanceDataPath()
|
|
|
|
|
{
|
|
|
|
|
string appName = Assembly.GetExecutingAssembly().GetName().Name;
|
|
|
|
|
|
|
|
|
|
string baseDataRoot = GetDataRootPath();
|
|
|
|
|
|
|
|
|
|
_instanceDataPath = Path.Combine(baseDataRoot, appName, "Instances", _instanceId);
|
|
|
|
|
|
|
|
|
|
Directory.CreateDirectory(_instanceDataPath);
|
|
|
|
|
Directory.CreateDirectory(Path.Combine(_instanceDataPath, "Data"));
|
|
|
|
|
Directory.CreateDirectory(Path.Combine(_instanceDataPath, "Logs"));
|
|
|
|
|
Directory.CreateDirectory(Path.Combine(_instanceDataPath, "Config"));
|
|
|
|
|
Directory.CreateDirectory(Path.Combine(_instanceDataPath, "Cache"));
|
|
|
|
|
Directory.CreateDirectory(Path.Combine(_instanceDataPath, "Temp"));
|
|
|
|
|
|
|
|
|
|
SaveInstanceInfo();
|
|
|
|
|
|
|
|
|
|
CleanupOldInstances();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 获取数据存储根路径
|
|
|
|
|
/// </summary>
|
|
|
|
|
private string GetDataRootPath()
|
|
|
|
|
{
|
|
|
|
|
string envPath = Environment.GetEnvironmentVariable("SLNMESNAC_DATA_ROOT");
|
|
|
|
|
if (!string.IsNullOrEmpty(envPath) && Directory.Exists(envPath))
|
|
|
|
|
return envPath;
|
|
|
|
|
|
|
|
|
|
return Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 保存实例信息文件
|
|
|
|
|
/// </summary>
|
|
|
|
|
private void SaveInstanceInfo()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine($"为实例 {instanceId} 设置特定配置...");
|
|
|
|
|
|
|
|
|
|
// 使用反射设置所有可能的配置属性
|
|
|
|
|
var configType = appConfig.GetType();
|
|
|
|
|
var properties = configType.GetProperties();
|
|
|
|
|
|
|
|
|
|
foreach (var property in properties)
|
|
|
|
|
var process = Process.GetCurrentProcess();
|
|
|
|
|
var info = new
|
|
|
|
|
{
|
|
|
|
|
if (property.CanWrite)
|
|
|
|
|
InstanceId = _instanceId,
|
|
|
|
|
StartTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
|
|
|
|
|
ProcessId = process.Id,
|
|
|
|
|
ProcessName = process.ProcessName,
|
|
|
|
|
MachineName = Environment.MachineName,
|
|
|
|
|
UserName = Environment.UserName,
|
|
|
|
|
WorkingDirectory = Environment.CurrentDirectory
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
string infoFile = Path.Combine(_instanceDataPath, "instance.info");
|
|
|
|
|
File.WriteAllText(infoFile,
|
|
|
|
|
$"Instance ID: {info.InstanceId}\r\n" +
|
|
|
|
|
$"Start Time: {info.StartTime}\r\n" +
|
|
|
|
|
$"Process ID: {info.ProcessId}\r\n" +
|
|
|
|
|
$"Process Name: {info.ProcessName}\r\n" +
|
|
|
|
|
$"Machine: {info.MachineName}\r\n" +
|
|
|
|
|
$"User: {info.UserName}\r\n" +
|
|
|
|
|
$"Working Dir: {info.WorkingDirectory}");
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Debug.WriteLine($"保存实例信息失败: {ex.Message}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 清理过期的实例数据
|
|
|
|
|
/// </summary>
|
|
|
|
|
private void CleanupOldInstances(int keepDays = 3)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
string appName = Assembly.GetExecutingAssembly().GetName().Name;
|
|
|
|
|
string instancesRoot = Path.Combine(
|
|
|
|
|
GetDataRootPath(),
|
|
|
|
|
appName,
|
|
|
|
|
"Instances"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!Directory.Exists(instancesRoot))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
DateTime cutoffTime = DateTime.Now.AddDays(-keepDays);
|
|
|
|
|
var instanceDirs = Directory.GetDirectories(instancesRoot);
|
|
|
|
|
|
|
|
|
|
foreach (var dir in instanceDirs)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var value = property.GetValue(appConfig);
|
|
|
|
|
if (value is string stringValue)
|
|
|
|
|
var dirInfo = new DirectoryInfo(dir);
|
|
|
|
|
var infoFile = Path.Combine(dir, "instance.info");
|
|
|
|
|
|
|
|
|
|
if (File.Exists(infoFile))
|
|
|
|
|
{
|
|
|
|
|
// 替换路径中的占位符
|
|
|
|
|
if (stringValue.Contains("{Instance}") ||
|
|
|
|
|
stringValue.Contains("{instance}"))
|
|
|
|
|
// 读取启动时间
|
|
|
|
|
var content = File.ReadAllText(infoFile);
|
|
|
|
|
var startTimeLine = content.Split('\n')
|
|
|
|
|
.FirstOrDefault(line => line.StartsWith("Start Time:"));
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrEmpty(startTimeLine))
|
|
|
|
|
{
|
|
|
|
|
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}");
|
|
|
|
|
string timeStr = startTimeLine.Substring(11).Trim();
|
|
|
|
|
if (DateTime.TryParse(timeStr, out DateTime startTime))
|
|
|
|
|
{
|
|
|
|
|
if (startTime < cutoffTime)
|
|
|
|
|
{
|
|
|
|
|
Directory.Delete(dir, true);
|
|
|
|
|
Debug.WriteLine($"已清理过期实例: {dirInfo.Name}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (property.PropertyType == typeof(int) &&
|
|
|
|
|
IsPortProperty(property.Name))
|
|
|
|
|
else if (dirInfo.CreationTime < cutoffTime)
|
|
|
|
|
{
|
|
|
|
|
// 调整端口号
|
|
|
|
|
var basePort = (int)value;
|
|
|
|
|
var newPort = basePort + (int.Parse(instanceId) - 1) * 10;
|
|
|
|
|
property.SetValue(appConfig, newPort);
|
|
|
|
|
Console.WriteLine($" 更新 {property.Name}: {newPort}");
|
|
|
|
|
// 如果没有信息文件,使用目录创建时间
|
|
|
|
|
Directory.Delete(dir, true);
|
|
|
|
|
Debug.WriteLine($"已清理过期实例: {dirInfo.Name}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Debug.WriteLine($"清理实例 {dir} 失败: {ex.Message}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine($"设置实例配置时出错: {ex.Message}");
|
|
|
|
|
//serilogHelper?.Info($"设置实例配置时出错: {ex.Message}");
|
|
|
|
|
Debug.WriteLine($"清理实例数据失败: {ex.Message}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
private static bool IsPortProperty(string propertyName)
|
|
|
|
|
{
|
|
|
|
|
return propertyName.ToLower().Contains("port");
|
|
|
|
|
}
|
|
|
|
|
private static bool IsPathProperty(string propertyName)
|
|
|
|
|
{
|
|
|
|
|
return propertyName.ToLower().Contains("path") ||
|
|
|
|
|
propertyName.ToLower().Contains("dir") ||
|
|
|
|
|
propertyName.ToLower().Contains("file");
|
|
|
|
|
}
|
|
|
|
|
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/{DateTime.UtcNow:yyyy-MM-dd}/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";
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// CreateHostBuilder
|
|
|
|
|
/// ConfigureServices
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="args"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
|
|
|
|
Host.CreateDefaultBuilder(args)
|
|
|
|
|
private void ConfigureServices(IServiceCollection services)
|
|
|
|
|
{
|
|
|
|
|
// 注册AppConfig
|
|
|
|
|
services.AddSingleton(provider =>
|
|
|
|
|
{
|
|
|
|
|
var configurationBuilder = new ConfigurationBuilder()
|
|
|
|
|
.SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
|
|
|
|
|
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
|
|
|
|
|
|
|
|
|
|
.UseSerilog()
|
|
|
|
|
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
|
|
|
|
|
.ConfigureWebHostDefaults(webBuilder =>
|
|
|
|
|
// 添加实例特定的配置文件(如果存在)
|
|
|
|
|
string instanceConfig = Path.Combine(_instanceDataPath, "Config", "appsettings.instance.json");
|
|
|
|
|
if (File.Exists(instanceConfig))
|
|
|
|
|
{
|
|
|
|
|
webBuilder.UseStartup<Startup>();
|
|
|
|
|
});
|
|
|
|
|
configurationBuilder.AddJsonFile(instanceConfig, optional: true, reloadOnChange: true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
IConfiguration configuration = configurationBuilder.Build();
|
|
|
|
|
var appConfig = configuration.GetSection("AppConfig").Get<AppConfig>();
|
|
|
|
|
|
|
|
|
|
// 设置实例特定的属性
|
|
|
|
|
if (appConfig != null)
|
|
|
|
|
{
|
|
|
|
|
appConfig.InstanceId = _instanceId;
|
|
|
|
|
appConfig.InstanceDataPath = _instanceDataPath;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return appConfig;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
//services.AddSingleton<SerilogHelper>();
|
|
|
|
|
|
|
|
|
|
// 加载程序集
|
|
|
|
|
Assembly[] assemblies = {
|
|
|
|
|
Assembly.LoadFrom("SlnMesnac.Repository.dll"),
|
|
|
|
|
Assembly.LoadFrom("SlnMesnac.Plc.dll"),
|
|
|
|
|
Assembly.LoadFrom("SlnMesnac.Rfid.dll"),
|
|
|
|
|
Assembly.LoadFrom("SlnMesnac.Common.dll"),
|
|
|
|
|
Assembly.LoadFrom("SlnMesnac.TouchSocket.dll"),
|
|
|
|
|
Assembly.LoadFrom("SlnMesnac.Business.dll"),
|
|
|
|
|
Assembly.LoadFrom("SlnMesnac.Generate.dll")
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 扫描并注册程序集中的服务
|
|
|
|
|
services.Scan(scan => scan.FromAssemblies(assemblies)
|
|
|
|
|
.AddClasses()
|
|
|
|
|
.AsImplementedInterfaces()
|
|
|
|
|
.AsSelf()
|
|
|
|
|
.WithTransientLifetime());
|
|
|
|
|
|
|
|
|
|
// 注册TcpService - 需要确保每个实例使用不同的端口
|
|
|
|
|
services.AddSingleton(provider =>
|
|
|
|
|
{
|
|
|
|
|
var tcpService = new TcpService();
|
|
|
|
|
// 根据实例ID调整端口配置
|
|
|
|
|
// 例如:tcpService.Port = GetInstanceSpecificPort(_instanceId);
|
|
|
|
|
return tcpService;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
services.AddLogging(x => x.AddSerilog());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 扫描并注册带有特性的窗口和服务
|
|
|
|
|
services.Scan(scan => scan
|
|
|
|
|
.FromAssemblyOf<MainWindow>()
|
|
|
|
|
.AddClasses(classes => classes.WithAttribute<RegisterAsSingletonAttribute>())
|
|
|
|
|
.AsSelf()
|
|
|
|
|
.WithSingletonLifetime());
|
|
|
|
|
|
|
|
|
|
services.Scan(scan => scan
|
|
|
|
|
.FromAssemblyOf<MainWindow>()
|
|
|
|
|
.AddClasses(classes => classes.WithAttribute<RegisterAsTransientAttribute>())
|
|
|
|
|
.AsSelf()
|
|
|
|
|
.WithTransientLifetime());
|
|
|
|
|
|
|
|
|
|
// 注册ORM
|
|
|
|
|
services.AddSqlSugarSetup();
|
|
|
|
|
|
|
|
|
|
services.AddRfidFactorySetup();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Exit事件
|
|
|
|
|
protected override void OnExit(ExitEventArgs e)
|
|
|
|
|
{
|
|
|
|
|
base.OnExit(e);
|
|
|
|
|
|
|
|
|
|
Log.Information($"系统退出,当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");
|
|
|
|
|
// 释放资源
|
|
|
|
|
// ...
|
|
|
|
|
Log.Information($"实例 {_instanceId} 退出,退出时间:{DateTime.Now:yyyy-MM-dd HH:mm:ss}");
|
|
|
|
|
|
|
|
|
|
// 释放互斥体
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
_instanceMutex?.ReleaseMutex();
|
|
|
|
|
_instanceMutex?.Dispose();
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Debug.WriteLine($"释放互斥体失败: {ex.Message}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
|
|
|
|
|
{
|
|
|
|
|
string errorMsg = $"[实例 {_instanceId}] 发生未处理异常: {e.Exception.Message}";
|
|
|
|
|
MessageBox.Show(errorMsg, "应用程序错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
|
|
|
|
|
|
|
|
Log.Error(e.Exception, $"实例 {_instanceId} 全局异常: {e.Exception.Message}");
|
|
|
|
|
|
|
|
|
|
// 保存异常信息到实例日志目录
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
string errorLogFile = Path.Combine(_instanceDataPath, "Logs", $"crash_{DateTime.Now:yyyyMMdd_HHmmss}.log");
|
|
|
|
|
File.WriteAllText(errorLogFile,
|
|
|
|
|
$"实例ID: {_instanceId}\r\n" +
|
|
|
|
|
$"异常时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}\r\n" +
|
|
|
|
|
$"异常信息: {e.Exception.Message}\r\n" +
|
|
|
|
|
$"堆栈跟踪:\r\n{e.Exception.StackTrace}");
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
// 忽略文件保存失败
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
e.Handled = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 静态辅助方法
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 启动一个新的应用程序实例
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static void StartNewInstance(string instanceId = null)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
string exePath = Process.GetCurrentProcess().MainModule?.FileName;
|
|
|
|
|
if (!string.IsNullOrEmpty(exePath))
|
|
|
|
|
{
|
|
|
|
|
string arguments = string.Empty;
|
|
|
|
|
if (!string.IsNullOrEmpty(instanceId))
|
|
|
|
|
{
|
|
|
|
|
arguments = $"/instance:{instanceId}";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ProcessStartInfo startInfo = new ProcessStartInfo
|
|
|
|
|
{
|
|
|
|
|
FileName = exePath,
|
|
|
|
|
Arguments = arguments,
|
|
|
|
|
UseShellExecute = true
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Process.Start(startInfo);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
MessageBox.Show($"启动新实例失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 获取所有实例的目录
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static string[] GetAllInstanceDirectories()
|
|
|
|
|
{
|
|
|
|
|
string appName = Assembly.GetExecutingAssembly().GetName().Name;
|
|
|
|
|
string instancesRoot = Path.Combine(
|
|
|
|
|
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
|
|
|
|
appName,
|
|
|
|
|
"Instances"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return Directory.Exists(instancesRoot)
|
|
|
|
|
? Directory.GetDirectories(instancesRoot)
|
|
|
|
|
: Array.Empty<string>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 获取所有运行中的实例信息
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static InstanceInfo[] GetAllRunningInstances()
|
|
|
|
|
{
|
|
|
|
|
var instanceDirs = GetAllInstanceDirectories();
|
|
|
|
|
var instances = new System.Collections.Generic.List<InstanceInfo>();
|
|
|
|
|
|
|
|
|
|
foreach (var dir in instanceDirs)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
string infoFile = Path.Combine(dir, "instance.info");
|
|
|
|
|
if (File.Exists(infoFile))
|
|
|
|
|
{
|
|
|
|
|
var content = File.ReadAllLines(infoFile);
|
|
|
|
|
var instanceId = Path.GetFileName(dir);
|
|
|
|
|
|
|
|
|
|
var info = new InstanceInfo
|
|
|
|
|
{
|
|
|
|
|
Id = instanceId,
|
|
|
|
|
DataPath = dir,
|
|
|
|
|
CreationTime = Directory.GetCreationTime(dir)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
instances.Add(info);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
// 忽略无法读取的实例
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return instances.ToArray();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 实例信息类
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class InstanceInfo
|
|
|
|
|
{
|
|
|
|
|
public string Id { get; set; }
|
|
|
|
|
public string DataPath { get; set; }
|
|
|
|
|
public DateTime CreationTime { get; set; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|