diff --git a/ConsoleApp/Startup.cs b/ConsoleApp/Startup.cs
index 926476f..5e3fd20 100644
--- a/ConsoleApp/Startup.cs
+++ b/ConsoleApp/Startup.cs
@@ -95,7 +95,7 @@ namespace ConsoleApp
}
//启用Serilog中间件
- app.UseSerilogExtensions();
+ //app.UseSerilogExtensions();
//app.UseTouchSocketExtensions();
diff --git a/SlnMesnac.Config/AppConfig.cs b/SlnMesnac.Config/AppConfig.cs
index 4a8ff4b..2f1780f 100644
--- a/SlnMesnac.Config/AppConfig.cs
+++ b/SlnMesnac.Config/AppConfig.cs
@@ -36,7 +36,11 @@ namespace SlnMesnac.Config
/// 日志文件路径
///
public string logPath { get; set; }
-
+
+ public string InstanceId { get; set; }
+
+ public string InstanceDataPath { get; set; }
+
///
/// Sql连接配置
///
diff --git a/SlnMesnac.Serilog/SerilogExtensions.cs b/SlnMesnac.Serilog/SerilogExtensions.cs
index 5e8dc03..c73c411 100644
--- a/SlnMesnac.Serilog/SerilogExtensions.cs
+++ b/SlnMesnac.Serilog/SerilogExtensions.cs
@@ -35,27 +35,27 @@ namespace SlnMesnac.Serilog
///
public static class SerilogExtensions
{
- public static IApplicationBuilder UseSerilogExtensions(this IApplicationBuilder app)
+ public static void UseSerilogExtensions(this IServiceProvider service)
{
- //启用Serilog中间件
- app.UseSerilogRequestLogging();
+
#region 通过配置文件读取日志存放位置
- var appConfig = app.ApplicationServices.GetService();
+ var appConfig = service.GetService();
var logPath = $"{appConfig.logPath}/Logs/";
+
#endregion
Log.Logger = new LoggerConfiguration().MinimumLevel.Information().WriteTo.Console()
- .WriteTo.File(Path.Combine(logPath, "Info.log"), LogEventLevel.Information,rollingInterval:RollingInterval.Day)
- .WriteTo.File(Path.Combine(logPath, "Error.log"), LogEventLevel.Error, rollingInterval: RollingInterval.Day)
- .WriteTo.File(Path.Combine(logPath, "data.log"), LogEventLevel.Warning, rollingInterval: RollingInterval.Day)
-
- //.WriteTo.File(Path.Combine(logPath, "data.log"), LogEventLevel.Debug)
- //.WriteTo.File(Path.Combine(logPath, "Debug.log"), LogEventLevel.Debug, fileSizeLimitBytes: 5 * 1024)
- .CreateLogger();
- app.UseSerilogRequestLogging();
-
- return app;
+ .WriteTo.Logger(lc => lc
+ .Filter.ByIncludingOnly(logEvent => logEvent.Properties.ContainsKey("Module") && logEvent.Properties["Module"].ToString().Contains("Info"))
+ .WriteTo.File(Path.Combine($"{logPath}/Info/", "Info.log"), rollingInterval: RollingInterval.Day))
+ .WriteTo.Logger(lc => lc
+ .Filter.ByIncludingOnly(logEvent => logEvent.Properties.ContainsKey("Module") && logEvent.Properties["Module"].ToString().Contains("data"))
+ .WriteTo.File(Path.Combine($"{logPath}/data/", "data.log"), rollingInterval: RollingInterval.Day))
+ .WriteTo.Logger(lc => lc
+ .Filter.ByIncludingOnly(logEvent => logEvent.Properties.ContainsKey("Module") && logEvent.Properties["Module"].ToString().Contains("Error"))
+ .WriteTo.File(Path.Combine($"{logPath}/Error/", "Error.log"), rollingInterval: RollingInterval.Day))
+ .CreateLogger();
}
}
}
diff --git a/SlnMesnac.WPF/App.xaml b/SlnMesnac.WPF/App.xaml
index 48f0a12..7cf1b93 100644
--- a/SlnMesnac.WPF/App.xaml
+++ b/SlnMesnac.WPF/App.xaml
@@ -1,8 +1,7 @@
+ xmlns:local="clr-namespace:SlnMesnac.WPF">
diff --git a/SlnMesnac.WPF/App.xaml.cs b/SlnMesnac.WPF/App.xaml.cs
index 151a30a..ee740ce 100644
--- a/SlnMesnac.WPF/App.xaml.cs
+++ b/SlnMesnac.WPF/App.xaml.cs
@@ -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
///
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();
+ 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.Info($"启动服务");
- //var appConfig = services.GetRequiredService();
- appConfig = host.Services.GetService();
-
- // 强制设置实例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();
+ 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)
+
+
+ ///
+ /// 从命令行参数获取实例ID
+ ///
+ 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;
+ }
+
+ ///
+ /// 生成实例ID
+ ///
+ private string GenerateInstanceId()
+ {
+ string timestamp = DateTime.Now.ToString("yyyyMMddHHmmss");
+ string random = new Random().Next(1000, 9999).ToString();
+ return $"{timestamp}_{random}";
+ }
+
+ ///
+ /// 初始化实例数据路径
+ ///
+ 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();
+ }
+
+ ///
+ /// 获取数据存储根路径
+ ///
+ 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 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}");
+ }
+ }
+
+ ///
+ /// 清理过期的实例数据
+ ///
+ 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();
-
- 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();
- }
///
- /// CreateHostBuilder
+ /// ConfigureServices
///
- ///
- ///
- 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();
- });
+ configurationBuilder.AddJsonFile(instanceConfig, optional: true, reloadOnChange: true);
+ }
+
+ IConfiguration configuration = configurationBuilder.Build();
+ var appConfig = configuration.GetSection("AppConfig").Get();
+
+ // 设置实例特定的属性
+ if (appConfig != null)
+ {
+ appConfig.InstanceId = _instanceId;
+ appConfig.InstanceDataPath = _instanceDataPath;
+ }
+
+ return appConfig;
+ });
+
+ //services.AddSingleton();
+
+ // 加载程序集
+ 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()
+ .AddClasses(classes => classes.WithAttribute())
+ .AsSelf()
+ .WithSingletonLifetime());
+
+ services.Scan(scan => scan
+ .FromAssemblyOf()
+ .AddClasses(classes => classes.WithAttribute())
+ .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;
+ }
+
+ // 静态辅助方法
+
+ ///
+ /// 启动一个新的应用程序实例
+ ///
+ 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);
+ }
+ }
+
+ ///
+ /// 获取所有实例的目录
+ ///
+ 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();
+ }
+
+ ///
+ /// 获取所有运行中的实例信息
+ ///
+ public static InstanceInfo[] GetAllRunningInstances()
+ {
+ var instanceDirs = GetAllInstanceDirectories();
+ var instances = new System.Collections.Generic.List();
+
+ 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();
+ }
+
+ ///
+ /// 实例信息类
+ ///
+ public class InstanceInfo
+ {
+ public string Id { get; set; }
+ public string DataPath { get; set; }
+ public DateTime CreationTime { get; set; }
}
diff --git a/SlnMesnac.WPF/Attribute/RegisterAsSingletonAttribute.cs b/SlnMesnac.WPF/Attribute/RegisterAsSingletonAttribute.cs
new file mode 100644
index 0000000..86eadf3
--- /dev/null
+++ b/SlnMesnac.WPF/Attribute/RegisterAsSingletonAttribute.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SlnMesnac.WPF.Attribute
+{
+ public class RegisterAsSingletonAttribute:System.Attribute
+ {
+ }
+}
diff --git a/SlnMesnac.WPF/Attribute/RegisterAsTransientAttribute.cs b/SlnMesnac.WPF/Attribute/RegisterAsTransientAttribute.cs
new file mode 100644
index 0000000..037555d
--- /dev/null
+++ b/SlnMesnac.WPF/Attribute/RegisterAsTransientAttribute.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SlnMesnac.WPF.Attribute
+{
+ public class RegisterAsTransientAttribute:System.Attribute
+ {
+ }
+}
diff --git a/SlnMesnac.WPF/FodyWeavers.xml b/SlnMesnac.WPF/FodyWeavers.xml
new file mode 100644
index 0000000..a6a2edf
--- /dev/null
+++ b/SlnMesnac.WPF/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/SlnMesnac.WPF/MainWindow.xaml.cs b/SlnMesnac.WPF/MainWindow.xaml.cs
index 7e480b5..13fbb25 100644
--- a/SlnMesnac.WPF/MainWindow.xaml.cs
+++ b/SlnMesnac.WPF/MainWindow.xaml.cs
@@ -18,12 +18,15 @@ using System.Windows.Shapes;
using System.Windows;
using System.Windows.Forms;
+using SlnMesnac.WPF.Attribute;
namespace SlnMesnac.WPF
{
///
/// Interaction logic for MainWindow.xaml
///
+ ///
+ [RegisterAsSingletonAttribute]
public partial class MainWindow : Window
{
//托盘
diff --git a/SlnMesnac.WPF/Page/IndexPage/AddTaskContent.xaml.cs b/SlnMesnac.WPF/Page/IndexPage/AddTaskContent.xaml.cs
index d185b75..dd1786c 100644
--- a/SlnMesnac.WPF/Page/IndexPage/AddTaskContent.xaml.cs
+++ b/SlnMesnac.WPF/Page/IndexPage/AddTaskContent.xaml.cs
@@ -1,6 +1,7 @@
using Microsoft.Extensions.DependencyInjection;
using SlnMesnac.Repository;
using SlnMesnac.Repository.service;
+using SlnMesnac.WPF.Attribute;
using SlnMesnac.WPF.ViewModel.IndexPage;
using System;
using System.Collections.Generic;
@@ -24,6 +25,7 @@ namespace SlnMesnac.WPF.Page.IndexPage
///
/// AddTaskContent.xaml 的交互逻辑
///
+ [RegisterAsSingletonAttribute]
public partial class AddTaskContent : Window
{
public Action _Taskaction;
diff --git a/SlnMesnac.WPF/Page/IndexPage/ChangeType.xaml.cs b/SlnMesnac.WPF/Page/IndexPage/ChangeType.xaml.cs
index d4b17e3..bd0d141 100644
--- a/SlnMesnac.WPF/Page/IndexPage/ChangeType.xaml.cs
+++ b/SlnMesnac.WPF/Page/IndexPage/ChangeType.xaml.cs
@@ -1,5 +1,6 @@
using ATC_MaterialBind.Entity;
using SlnMesnac.Repository;
+using SlnMesnac.WPF.Attribute;
using SlnMesnac.WPF.ViewModel.IndexPage;
using System;
using System.Collections.Generic;
@@ -21,6 +22,7 @@ namespace SlnMesnac.WPF.Page.IndexPage
///
/// ChangeType.xaml 的交互逻辑
///
+ [RegisterAsSingletonAttribute]
public partial class ChangeType : UserControl
{
ChangeTypeViewModel ChangeTypeView;
diff --git a/SlnMesnac.WPF/Page/IndexPage/DetailTaskContent.xaml.cs b/SlnMesnac.WPF/Page/IndexPage/DetailTaskContent.xaml.cs
index 87c73db..b47ec17 100644
--- a/SlnMesnac.WPF/Page/IndexPage/DetailTaskContent.xaml.cs
+++ b/SlnMesnac.WPF/Page/IndexPage/DetailTaskContent.xaml.cs
@@ -17,12 +17,14 @@ using System.Windows.Shapes;
using static Microsoft.WindowsAPICodePack.Shell.PropertySystem.SystemProperties.System;
using Microsoft.Extensions.DependencyInjection;
using Task = System.Threading.Tasks.Task;
+using SlnMesnac.WPF.Attribute;
namespace SlnMesnac.WPF.Page.IndexPage
{
///
/// DetailTaskContent.xaml 的交互逻辑
///
+ [RegisterAsSingletonAttribute]
public partial class DetailTaskContent : Window
{
public Action _Taskaction;
diff --git a/SlnMesnac.WPF/Page/IndexPage/IndexContent.xaml.cs b/SlnMesnac.WPF/Page/IndexPage/IndexContent.xaml.cs
index 718adf4..f7ac7e0 100644
--- a/SlnMesnac.WPF/Page/IndexPage/IndexContent.xaml.cs
+++ b/SlnMesnac.WPF/Page/IndexPage/IndexContent.xaml.cs
@@ -1,4 +1,5 @@
-using SlnMesnac.WPF.ViewModel.IndexPage;
+using SlnMesnac.WPF.Attribute;
+using SlnMesnac.WPF.ViewModel.IndexPage;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -19,6 +20,7 @@ namespace SlnMesnac.WPF.Page.IndexPage
///
/// IndexContent.xaml 的交互逻辑
///
+ [RegisterAsSingletonAttribute]
public partial class IndexContent : UserControl
{
public IndexContent()
diff --git a/SlnMesnac.WPF/Page/IndexPage/MaterialBind.xaml.cs b/SlnMesnac.WPF/Page/IndexPage/MaterialBind.xaml.cs
index 5c656ae..762416b 100644
--- a/SlnMesnac.WPF/Page/IndexPage/MaterialBind.xaml.cs
+++ b/SlnMesnac.WPF/Page/IndexPage/MaterialBind.xaml.cs
@@ -1,5 +1,6 @@
using ATC_MaterialBind.Entity;
using SlnMesnac.Plc;
+using SlnMesnac.WPF.Attribute;
using SlnMesnac.WPF.ViewModel.IndexPage;
using System;
using System.Collections.Generic;
@@ -22,6 +23,7 @@ namespace SlnMesnac.WPF.Page.IndexPage
///
/// MaterialBind.xaml 的交互逻辑
///
+ [RegisterAsSingletonAttribute]
public partial class MaterialBind : UserControl
{
IndexContentViewModel indexContentViewModel;
diff --git a/SlnMesnac.WPF/Page/IndexPage/MiddleWare.xaml.cs b/SlnMesnac.WPF/Page/IndexPage/MiddleWare.xaml.cs
index 6da4ca9..88396ce 100644
--- a/SlnMesnac.WPF/Page/IndexPage/MiddleWare.xaml.cs
+++ b/SlnMesnac.WPF/Page/IndexPage/MiddleWare.xaml.cs
@@ -4,6 +4,7 @@ using SlnMesnac.Model.domain;
using SlnMesnac.Model.dto;
using SlnMesnac.Plc;
using SlnMesnac.Rfid;
+using SlnMesnac.WPF.Attribute;
using SlnMesnac.WPF.Model;
using SlnMesnac.WPF.ViewModel.IndexPage;
using System;
@@ -27,6 +28,7 @@ namespace SlnMesnac.WPF.Page.IndexPage
///
/// MaterialBind.xaml 的交互逻辑
///
+ [RegisterAsSingletonAttribute]
public partial class MiddleWare : UserControl
{
public List rfidList;
diff --git a/SlnMesnac.WPF/Page/IndexPage/SetPower.xaml.cs b/SlnMesnac.WPF/Page/IndexPage/SetPower.xaml.cs
index c0a0d8c..04f509f 100644
--- a/SlnMesnac.WPF/Page/IndexPage/SetPower.xaml.cs
+++ b/SlnMesnac.WPF/Page/IndexPage/SetPower.xaml.cs
@@ -1,5 +1,6 @@
using Microsoft.Extensions.DependencyInjection;
using SlnMesnac.Rfid;
+using SlnMesnac.WPF.Attribute;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -19,6 +20,7 @@ namespace SlnMesnac.WPF.Page.IndexPage
///
/// SetPower.xaml 的交互逻辑
///
+ [RegisterAsSingletonAttribute]
public partial class SetPower : Window
{
public List rfidList;
diff --git a/SlnMesnac.WPF/SlnMesnac.WPF.csproj b/SlnMesnac.WPF/SlnMesnac.WPF.csproj
index 1a36ef7..a27b950 100644
--- a/SlnMesnac.WPF/SlnMesnac.WPF.csproj
+++ b/SlnMesnac.WPF/SlnMesnac.WPF.csproj
@@ -43,11 +43,12 @@
-
+
+
diff --git a/SlnMesnac.WPF/Startup.cs b/SlnMesnac.WPF/Startup.cs
index 336c170..66abe93 100644
--- a/SlnMesnac.WPF/Startup.cs
+++ b/SlnMesnac.WPF/Startup.cs
@@ -81,7 +81,7 @@ namespace SlnMesnac.WPF
}
//启用Serilog中间件
- app.UseSerilogExtensions();
+ //app.UseSerilogExtensions();
app.UseTouchSocketExtensions();
//app.UseTouchSocketExtensions();
diff --git a/SlnMesnac.WPF/ViewModel/Generate/GenerateControlViewModel.cs b/SlnMesnac.WPF/ViewModel/Generate/GenerateControlViewModel.cs
index e6e18cc..bd6e92a 100644
--- a/SlnMesnac.WPF/ViewModel/Generate/GenerateControlViewModel.cs
+++ b/SlnMesnac.WPF/ViewModel/Generate/GenerateControlViewModel.cs
@@ -5,6 +5,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.WindowsAPICodePack.Dialogs;
using SlnMesnac.Config;
using SlnMesnac.Generate;
+using SlnMesnac.WPF.Attribute;
using SqlSugar;
using System;
using System.Collections.Generic;
@@ -38,6 +39,7 @@ using System.Windows;
#endregion << 版 本 注 释 >>
namespace SlnMesnac.WPF.ViewModel.Generate
{
+ [RegisterAsSingletonAttribute]
internal class GenerateControlViewModel : ViewModelBase
{
private readonly AppConfig _appConfig;
diff --git a/SlnMesnac.WPF/ViewModel/IndexPage/ChangeTypeViewModel.cs b/SlnMesnac.WPF/ViewModel/IndexPage/ChangeTypeViewModel.cs
index e1ca6c3..09ab90d 100644
--- a/SlnMesnac.WPF/ViewModel/IndexPage/ChangeTypeViewModel.cs
+++ b/SlnMesnac.WPF/ViewModel/IndexPage/ChangeTypeViewModel.cs
@@ -6,6 +6,7 @@ using Microsoft.Extensions.Logging;
using SlnMesnac.Config;
using SlnMesnac.Repository;
using SlnMesnac.Repository.service;
+using SlnMesnac.WPF.Attribute;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@@ -15,6 +16,7 @@ using System.Threading.Tasks;
namespace SlnMesnac.WPF.ViewModel.IndexPage
{
+ [RegisterAsSingletonAttribute]
public partial class ChangeTypeViewModel : ObservableObject
{
private ILogger _logger;
diff --git a/SlnMesnac.WPF/ViewModel/IndexPage/IndexContentViewModel.cs b/SlnMesnac.WPF/ViewModel/IndexPage/IndexContentViewModel.cs
index abf125f..4c1eb9f 100644
--- a/SlnMesnac.WPF/ViewModel/IndexPage/IndexContentViewModel.cs
+++ b/SlnMesnac.WPF/ViewModel/IndexPage/IndexContentViewModel.cs
@@ -34,6 +34,7 @@ using System.Data;
using System.Dynamic;
using System.Drawing;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.TaskbarClock;
+using SlnMesnac.WPF.Attribute;
#region << 版 本 注 释 >>
/*--------------------------------------------------------------------
@@ -59,6 +60,7 @@ using static System.Windows.Forms.VisualStyles.VisualStyleElement.TaskbarClock;
#endregion << 版 本 注 释 >>
namespace SlnMesnac.WPF.ViewModel.IndexPage
{
+ [RegisterAsSingletonAttribute]
public partial class IndexContentViewModel : ObservableObject
{
private ILogger _logger;
diff --git a/SlnMesnac.WPF/ViewModel/IndexPage/MiddleWareViewModel.cs b/SlnMesnac.WPF/ViewModel/IndexPage/MiddleWareViewModel.cs
index 91c1f2a..20fe28b 100644
--- a/SlnMesnac.WPF/ViewModel/IndexPage/MiddleWareViewModel.cs
+++ b/SlnMesnac.WPF/ViewModel/IndexPage/MiddleWareViewModel.cs
@@ -9,6 +9,7 @@ using SlnMesnac.Config;
using SlnMesnac.Repository;
using SlnMesnac.Rfid;
using SlnMesnac.TouchSocket;
+using SlnMesnac.WPF.Attribute;
using SlnMesnac.WPF.Model;
using SlnMesnac.WPF.Page.IndexPage;
using SqlSugar;
@@ -27,6 +28,7 @@ using Task = System.Threading.Tasks.Task;
namespace SlnMesnac.WPF.ViewModel.IndexPage
{
+ [RegisterAsSingletonAttribute]
public partial class MiddleWareViewModel : ViewModelBase
{
private ILogger _logger;
diff --git a/SlnMesnac.WPF/ViewModel/MainWindowViewModel.cs b/SlnMesnac.WPF/ViewModel/MainWindowViewModel.cs
index bb476f6..58a8856 100644
--- a/SlnMesnac.WPF/ViewModel/MainWindowViewModel.cs
+++ b/SlnMesnac.WPF/ViewModel/MainWindowViewModel.cs
@@ -9,6 +9,7 @@ using SlnMesnac.Extensions;
using SlnMesnac.Plc;
using SlnMesnac.Repository.service;
using SlnMesnac.TouchSocket;
+using SlnMesnac.WPF.Attribute;
using SlnMesnac.WPF.Page.Generate;
using SlnMesnac.WPF.Page.IndexPage;
using System;
@@ -16,6 +17,7 @@ using System.Windows;
namespace SlnMesnac.WPF.ViewModel
{
+ [RegisterAsSingletonAttribute]
public class MainWindowViewModel: ViewModelBase
{
private readonly ILogger _logger;
diff --git a/SlnMesnac/Startup.cs b/SlnMesnac/Startup.cs
index 094e00d..7560390 100644
--- a/SlnMesnac/Startup.cs
+++ b/SlnMesnac/Startup.cs
@@ -106,7 +106,7 @@ namespace SlnMesnac
});
//启用Serilog中间件
- app.UseSerilogExtensions();
+ //app.UseSerilogExtensions();
//app.UseHttpsRedirection();