|
|
using Lierda.WPFHelper;
|
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
using Serilog;
|
|
|
using SlnMesnac.Config;
|
|
|
using System;
|
|
|
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;
|
|
|
using SlnMesnac.TouchSocket;
|
|
|
using SlnMesnac.Quartz;
|
|
|
|
|
|
namespace SlnMesnac.WPF
|
|
|
{
|
|
|
/// <summary>
|
|
|
/// Interaction logic for App.xaml
|
|
|
/// </summary>
|
|
|
public partial class App : Application
|
|
|
{
|
|
|
private Mutex _instanceMutex = null;
|
|
|
private LierdaCracker cracker = new LierdaCracker();
|
|
|
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
|
|
|
{
|
|
|
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);
|
|
|
|
|
|
// 设置ServiceCollection
|
|
|
var services = new ServiceCollection();
|
|
|
ConfigureServices(services); // 配置服务
|
|
|
|
|
|
// 创建ServiceProvider
|
|
|
ServiceProvider = services.BuildServiceProvider();
|
|
|
|
|
|
// 配置Serilog和其他扩展
|
|
|
ServiceProvider.UseSerilogExtensions();
|
|
|
ServiceProvider.UseTouchSocketExtensions();
|
|
|
|
|
|
// 获取AppConfig并更新实例特定的配置
|
|
|
var appConfig = ServiceProvider.GetService<AppConfig>();
|
|
|
if (appConfig != null)
|
|
|
{
|
|
|
// 更新日志路径为实例专用路径
|
|
|
appConfig.logPath = Path.Combine(_instanceDataPath, "Logs");
|
|
|
Directory.CreateDirectory(appConfig.logPath);
|
|
|
|
|
|
// 更新其他可能需要实例化的路径
|
|
|
appConfig.InstanceId = _instanceId;
|
|
|
appConfig.InstanceDataPath = _instanceDataPath;
|
|
|
|
|
|
// 根据实例ID调整数据库连接(如果需要)
|
|
|
// AdjustDatabaseConnectionForInstance(appConfig);
|
|
|
}
|
|
|
// 显示主窗口
|
|
|
var loginWindow = ServiceProvider.GetRequiredService<MainWindow>();
|
|
|
loginWindow.WindowStartupLocation = WindowStartupLocation.CenterScreen;
|
|
|
|
|
|
// 在窗口标题显示实例信息
|
|
|
loginWindow.Title = $"{loginWindow.Title} [{_instanceId}]";
|
|
|
loginWindow.Show();
|
|
|
|
|
|
var serilog = ServiceProvider.GetRequiredService<SerilogHelper>();
|
|
|
|
|
|
serilog.Info($"应用程序启动 - 实例ID: {_instanceId}, 进程ID: {Process.GetCurrentProcess().Id}");
|
|
|
serilog.Info($"日志目录: {appConfig?.logPath}");
|
|
|
//serilog.Data($"数据目录: {_instanceDataPath}");
|
|
|
//serilog.Error($"日志目录: {appConfig?.logPath}");
|
|
|
}
|
|
|
catch (Exception exception)
|
|
|
{
|
|
|
Console.WriteLine(exception);
|
|
|
throw;
|
|
|
}
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
/// <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);
|
|
|
//}
|
|
|
|
|
|
private string GetDataRootPath()
|
|
|
{
|
|
|
// 使用当前系统目录 + InstanceData 路径
|
|
|
string currentDirectory = AppDomain.CurrentDomain.BaseDirectory;
|
|
|
string instanceDataPath = Path.Combine(currentDirectory, "InstanceData");
|
|
|
|
|
|
// 如果无法访问当前目录,使用应用程序数据目录作为后备
|
|
|
try
|
|
|
{
|
|
|
// 尝试访问当前目录,确保我们有权限
|
|
|
if (!Directory.Exists(instanceDataPath))
|
|
|
{
|
|
|
Directory.CreateDirectory(instanceDataPath);
|
|
|
}
|
|
|
return instanceDataPath;
|
|
|
}
|
|
|
catch
|
|
|
{
|
|
|
// 如果没有权限,使用用户应用程序数据目录
|
|
|
string fallbackPath = Path.Combine(
|
|
|
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
|
|
Assembly.GetExecutingAssembly().GetName().Name,
|
|
|
"InstanceData"
|
|
|
);
|
|
|
Directory.CreateDirectory(fallbackPath);
|
|
|
return fallbackPath;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// 保存实例信息文件
|
|
|
/// </summary>
|
|
|
private void SaveInstanceInfo()
|
|
|
{
|
|
|
try
|
|
|
{
|
|
|
var process = Process.GetCurrentProcess();
|
|
|
var info = new
|
|
|
{
|
|
|
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 dirInfo = new DirectoryInfo(dir);
|
|
|
var infoFile = Path.Combine(dir, "instance.info");
|
|
|
|
|
|
if (File.Exists(infoFile))
|
|
|
{
|
|
|
// 读取启动时间
|
|
|
var content = File.ReadAllText(infoFile);
|
|
|
var startTimeLine = content.Split('\n')
|
|
|
.FirstOrDefault(line => line.StartsWith("Start Time:"));
|
|
|
|
|
|
if (!string.IsNullOrEmpty(startTimeLine))
|
|
|
{
|
|
|
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 (dirInfo.CreationTime < cutoffTime)
|
|
|
{
|
|
|
// 如果没有信息文件,使用目录创建时间
|
|
|
Directory.Delete(dir, true);
|
|
|
Debug.WriteLine($"已清理过期实例: {dirInfo.Name}");
|
|
|
}
|
|
|
}
|
|
|
catch (Exception ex)
|
|
|
{
|
|
|
Debug.WriteLine($"清理实例 {dir} 失败: {ex.Message}");
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
catch (Exception ex)
|
|
|
{
|
|
|
Debug.WriteLine($"清理实例数据失败: {ex.Message}");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// ConfigureServices
|
|
|
/// </summary>
|
|
|
private void ConfigureServices(IServiceCollection services)
|
|
|
{
|
|
|
// 注册AppConfig
|
|
|
services.AddSingleton(provider =>
|
|
|
{
|
|
|
var configurationBuilder = new ConfigurationBuilder()
|
|
|
.SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
|
|
|
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
|
|
|
|
|
|
// 添加实例特定的配置文件(如果存在)
|
|
|
string instanceConfig = Path.Combine(_instanceDataPath, "Config", "appsettings.instance.json");
|
|
|
if (File.Exists(instanceConfig))
|
|
|
{
|
|
|
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($"实例 {_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; }
|
|
|
}
|
|
|
|
|
|
|
|
|
}
|
|
|
}
|