You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

524 lines
19 KiB
C#

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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;
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();
// 获取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);
}
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)
{
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);
}
/// <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; }
}
}
}