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();