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.

558 lines
20 KiB
C#

using Lierda.WPFHelper;
2 years ago
using Microsoft.Extensions.DependencyInjection;
using Serilog;
using SlnMesnac.Config;
2 months ago
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;
2 years ago
namespace SlnMesnac.WPF
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
private Mutex _instanceMutex = null;
2 years ago
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;
2 years ago
// Startup事件
protected override async void OnStartup(StartupEventArgs e)
{
1 year ago
try
2 years ago
{
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
2 years ago
1 year ago
cracker.Cracker(100); //设置GC回收间隔
2 years ago
1 year ago
base.OnStartup(e);
2 months ago
// 设置ServiceCollection
var services = new ServiceCollection();
ConfigureServices(services); // 配置服务
// 创建ServiceProvider
ServiceProvider = services.BuildServiceProvider();
2 months ago
// 配置Serilog和其他扩展
ServiceProvider.UseSerilogExtensions();
ServiceProvider.UseTouchSocketExtensions();
2 months ago
// 获取AppConfig并更新实例特定的配置
var appConfig = ServiceProvider.GetService<AppConfig>();
if (appConfig != null)
2 months ago
{
// 更新日志路径为实例专用路径
appConfig.logPath = Path.Combine(_instanceDataPath, "Logs");
Directory.CreateDirectory(appConfig.logPath);
2 months ago
// 更新其他可能需要实例化的路径
appConfig.InstanceId = _instanceId;
appConfig.InstanceDataPath = _instanceDataPath;
2 years ago
// 根据实例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}");
1 year ago
}
catch (Exception exception)
{
Console.WriteLine(exception);
throw;
}
2 years ago
}
/// <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)
2 months ago
{
try
{
string appName = Assembly.GetExecutingAssembly().GetName().Name;
string instancesRoot = Path.Combine(
GetDataRootPath(),
appName,
"Instances"
);
2 months ago
if (!Directory.Exists(instancesRoot))
return;
2 months ago
DateTime cutoffTime = DateTime.Now.AddDays(-keepDays);
var instanceDirs = Directory.GetDirectories(instancesRoot);
foreach (var dir in instanceDirs)
2 months ago
{
try
2 months ago
{
var dirInfo = new DirectoryInfo(dir);
var infoFile = Path.Combine(dir, "instance.info");
if (File.Exists(infoFile))
2 months ago
{
// 读取启动时间
var content = File.ReadAllText(infoFile);
var startTimeLine = content.Split('\n')
.FirstOrDefault(line => line.StartsWith("Start Time:"));
if (!string.IsNullOrEmpty(startTimeLine))
2 months ago
{
string timeStr = startTimeLine.Substring(11).Trim();
if (DateTime.TryParse(timeStr, out DateTime startTime))
{
if (startTime < cutoffTime)
{
Directory.Delete(dir, true);
Debug.WriteLine($"已清理过期实例: {dirInfo.Name}");
}
}
2 months ago
}
}
else if (dirInfo.CreationTime < cutoffTime)
2 months ago
{
// 如果没有信息文件,使用目录创建时间
Directory.Delete(dir, true);
Debug.WriteLine($"已清理过期实例: {dirInfo.Name}");
2 months ago
}
}
catch (Exception ex)
{
Debug.WriteLine($"清理实例 {dir} 失败: {ex.Message}");
}
2 months ago
}
}
catch (Exception ex)
{
Debug.WriteLine($"清理实例数据失败: {ex.Message}");
2 months ago
}
}
/// <summary>
/// ConfigureServices
/// </summary>
private void ConfigureServices(IServiceCollection services)
2 months ago
{
// 注册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();
2 months ago
}
// Exit事件
protected override void OnExit(ExitEventArgs e)
2 months ago
{
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}");
}
2 months ago
}
private void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
2 months ago
{
string errorMsg = $"[实例 {_instanceId}] 发生未处理异常: {e.Exception.Message}";
MessageBox.Show(errorMsg, "应用程序错误", MessageBoxButton.OK, MessageBoxImage.Error);
2 months ago
Log.Error(e.Exception, $"实例 {_instanceId} 全局异常: {e.Exception.Message}");
// 保存异常信息到实例日志目录
try
2 months ago
{
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
{
// 忽略文件保存失败
}
2 months ago
e.Handled = true;
}
// 静态辅助方法
/// <summary>
/// 启动一个新的应用程序实例
/// </summary>
public static void StartNewInstance(string instanceId = null)
2 months ago
{
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()
2 months ago
{
string appName = Assembly.GetExecutingAssembly().GetName().Name;
string instancesRoot = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
appName,
"Instances"
);
2 months ago
return Directory.Exists(instancesRoot)
? Directory.GetDirectories(instancesRoot)
: Array.Empty<string>();
2 months ago
}
/// <summary>
/// 获取所有运行中的实例信息
/// </summary>
public static InstanceInfo[] GetAllRunningInstances()
2 months ago
{
var instanceDirs = GetAllInstanceDirectories();
var instances = new System.Collections.Generic.List<InstanceInfo>();
foreach (var dir in instanceDirs)
2 months ago
{
try
2 months ago
{
string infoFile = Path.Combine(dir, "instance.info");
if (File.Exists(infoFile))
2 months ago
{
var content = File.ReadAllLines(infoFile);
var instanceId = Path.GetFileName(dir);
var info = new InstanceInfo
2 months ago
{
Id = instanceId,
DataPath = dir,
CreationTime = Directory.GetCreationTime(dir)
};
instances.Add(info);
2 months ago
}
}
catch
{
// 忽略无法读取的实例
}
2 months ago
}
2 years ago
return instances.ToArray();
2 months ago
}
2 years ago
/// <summary>
/// 实例信息类
2 years ago
/// </summary>
public class InstanceInfo
2 years ago
{
public string Id { get; set; }
public string DataPath { get; set; }
public DateTime CreationTime { get; set; }
2 years ago
}
}
}