2026-04-28 10:23:58 +08:00
|
|
|
|
|
|
|
|
|
|
using FluentValidation;
|
|
|
|
|
|
using Mapster;
|
|
|
|
|
|
using Microsoft.Extensions.Configuration;
|
|
|
|
|
|
using NewLife;
|
2026-05-18 17:31:18 +08:00
|
|
|
|
using System.Configuration;
|
2026-04-28 10:23:58 +08:00
|
|
|
|
using System.IO;
|
2026-05-15 17:30:30 +08:00
|
|
|
|
using System.Text;
|
2026-04-28 10:23:58 +08:00
|
|
|
|
using System.Windows;
|
2026-05-15 17:30:30 +08:00
|
|
|
|
using System.Windows.Threading;
|
2026-04-28 10:23:58 +08:00
|
|
|
|
using YY.Admin.Core;
|
|
|
|
|
|
using YY.Admin.EventBus;
|
|
|
|
|
|
using YY.Admin.Filter;
|
|
|
|
|
|
using YY.Admin.Module;
|
|
|
|
|
|
using YY.Admin.Properties;
|
2026-05-12 18:29:03 +08:00
|
|
|
|
using YY.Admin.Services.Service.Print;
|
2026-04-28 10:23:58 +08:00
|
|
|
|
using YY.Admin.Setup;
|
|
|
|
|
|
using YY.Admin.ViewModels;
|
|
|
|
|
|
using YY.Admin.Views;
|
2026-05-15 17:30:30 +08:00
|
|
|
|
using YY.Admin.Core.Util;
|
2026-04-28 10:23:58 +08:00
|
|
|
|
namespace YY.Admin
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Interaction logic for App.xaml
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public partial class App : PrismApplication
|
|
|
|
|
|
{
|
|
|
|
|
|
private IConfiguration? _configuration;
|
|
|
|
|
|
private ILoggerService? _logger;
|
|
|
|
|
|
private readonly SyncModule _syncModule = new();
|
2026-05-15 17:30:30 +08:00
|
|
|
|
|
|
|
|
|
|
public App()
|
|
|
|
|
|
{
|
|
|
|
|
|
DispatcherUnhandledException += OnDispatcherUnhandledException;
|
|
|
|
|
|
AppDomain.CurrentDomain.UnhandledException += OnUnhandledDomainException;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void TryWriteCrashLog(string headline, Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var dir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "YY.Admin", "logs");
|
|
|
|
|
|
Directory.CreateDirectory(dir);
|
|
|
|
|
|
var file = Path.Combine(dir, $"crash-{DateTime.Now:yyyyMMdd-HHmmss}.txt");
|
|
|
|
|
|
var sb = new StringBuilder();
|
|
|
|
|
|
sb.AppendLine(headline);
|
|
|
|
|
|
sb.AppendLine(ex.ToString());
|
|
|
|
|
|
File.WriteAllText(file, sb.ToString(), Encoding.UTF8);
|
|
|
|
|
|
MessageBox.Show($"程序异常已写入日志:\n{file}\n\n{ex.Message}", "智能制造MES工控", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch
|
|
|
|
|
|
{
|
|
|
|
|
|
MessageBox.Show($"{headline}\n{ex}", "智能制造MES工控", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
TryWriteCrashLog("UI 线程未处理异常", e.Exception);
|
|
|
|
|
|
e.Handled = true;
|
|
|
|
|
|
Shutdown(1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnUnhandledDomainException(object? sender, UnhandledExceptionEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (e.ExceptionObject is Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
TryWriteCrashLog("域未处理异常", ex);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (e.IsTerminating)
|
|
|
|
|
|
{
|
|
|
|
|
|
Environment.Exit(1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-18 17:31:18 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 写入用户级 AppSettings(user.config)。若文件损坏(如缺少 configSections),删除后重试一次。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private static void PersistSkinTypeUserSettingSafe()
|
|
|
|
|
|
{
|
|
|
|
|
|
for (var attempt = 0; attempt < 2; attempt++)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
AppSettings.Default.SkinType = AppSettingsViewModel.GetSkinType().ToInt();
|
|
|
|
|
|
AppSettings.Default.Save();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (ConfigurationErrorsException ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (attempt == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
var path = ex.Filename;
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(path) && ex.InnerException is ConfigurationErrorsException inner)
|
|
|
|
|
|
path = inner.Filename;
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(path) && File.Exists(path))
|
|
|
|
|
|
File.Delete(path);
|
|
|
|
|
|
AppSettings.Default.Reload();
|
|
|
|
|
|
}
|
|
|
|
|
|
catch
|
|
|
|
|
|
{
|
|
|
|
|
|
// 删除/Reload 失败则向上抛出原异常
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
throw;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-28 10:23:58 +08:00
|
|
|
|
protected override Window CreateShell()
|
|
|
|
|
|
{
|
|
|
|
|
|
return Container.Resolve<LoginWindow>();
|
|
|
|
|
|
}
|
|
|
|
|
|
protected override void OnStartup(StartupEventArgs e)
|
|
|
|
|
|
{
|
2026-05-15 17:30:30 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
|
|
|
|
|
|
var cfgPath = Path.Combine(baseDirectory, "Configuration", "appsettings.json");
|
|
|
|
|
|
if (!File.Exists(cfgPath))
|
|
|
|
|
|
{
|
|
|
|
|
|
var msg = $"未找到配置文件(请确认与 YY.Admin.exe 同目录存在 Configuration\\appsettings.json):\n{cfgPath}";
|
|
|
|
|
|
TryWriteStartupFailure(msg);
|
|
|
|
|
|
Shutdown(1);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var userCfgPath = Path.Combine(
|
|
|
|
|
|
AppWritablePaths.EnsureDirectoryExists(AppWritablePaths.ConfigurationDirectory),
|
|
|
|
|
|
"appsettings.json");
|
|
|
|
|
|
|
|
|
|
|
|
// 构建配置:安装目录默认 + 用户目录覆盖(JeecgIntegration 等可由服务器设置写入)
|
|
|
|
|
|
_configuration = new ConfigurationBuilder()
|
|
|
|
|
|
.SetBasePath(baseDirectory)
|
|
|
|
|
|
.AddJsonFile(Path.Combine("Configuration", "appsettings.json"), optional: false, reloadOnChange: true)
|
|
|
|
|
|
.AddJsonFile(userCfgPath, optional: true, reloadOnChange: true)
|
|
|
|
|
|
.Build();
|
|
|
|
|
|
// 全局配置
|
|
|
|
|
|
TypeAdapterConfig.GlobalSettings.Default
|
|
|
|
|
|
.IgnoreNullValues(true)
|
|
|
|
|
|
.NameMatchingStrategy(NameMatchingStrategy.IgnoreCase);
|
|
|
|
|
|
|
|
|
|
|
|
// FluentValidation 全局规则级别配置
|
|
|
|
|
|
ValidatorOptions.Global.DefaultRuleLevelCascadeMode = CascadeMode.Stop;
|
|
|
|
|
|
|
|
|
|
|
|
// Mapster 全局配置
|
|
|
|
|
|
#if DEBUG
|
|
|
|
|
|
//TypeAdapterConfig.GlobalSettings.RequireExplicitMapping = true;
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
base.OnStartup(e);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
TryWriteCrashLog("启动阶段异常(配置/框架初始化失败)", ex);
|
|
|
|
|
|
Shutdown(1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void TryWriteStartupFailure(string message)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var dir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "YY.Admin", "logs");
|
|
|
|
|
|
Directory.CreateDirectory(dir);
|
|
|
|
|
|
var file = Path.Combine(dir, $"startup-{DateTime.Now:yyyyMMdd-HHmmss}.txt");
|
|
|
|
|
|
File.WriteAllText(file, message, Encoding.UTF8);
|
|
|
|
|
|
MessageBox.Show($"{message}\n\n已记录:{file}", "智能制造MES工控", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch
|
|
|
|
|
|
{
|
|
|
|
|
|
MessageBox.Show(message, "智能制造MES工控", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
|
|
|
|
}
|
2026-04-28 10:23:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
//注册
|
|
|
|
|
|
protected override void RegisterTypes(IContainerRegistry containerRegistry)
|
|
|
|
|
|
{
|
|
|
|
|
|
//后续调整为配置依赖注入(Prism Module)
|
|
|
|
|
|
// 注册错误处理
|
|
|
|
|
|
containerRegistry.RegisterSingleton<IErrorHandler, ErrorHandler>();
|
|
|
|
|
|
//全局错误处理
|
|
|
|
|
|
containerRegistry.RegisterSingleton<GlobalExceptionHandler>();
|
|
|
|
|
|
|
|
|
|
|
|
// 注册事件聚合器(Prism自带)
|
|
|
|
|
|
containerRegistry.RegisterSingleton<IEventAggregator, EventAggregator>();
|
|
|
|
|
|
|
|
|
|
|
|
//项目配置选项
|
|
|
|
|
|
containerRegistry.AddProjectOptions(_configuration!);
|
|
|
|
|
|
// 注册原始配置,供业务服务读取第三方对接参数
|
|
|
|
|
|
containerRegistry.RegisterInstance(_configuration!);
|
|
|
|
|
|
// 注册缓存服务
|
|
|
|
|
|
containerRegistry.AddNewLifeCache(_configuration!);
|
|
|
|
|
|
//注册数据库服务
|
|
|
|
|
|
containerRegistry.AddDbContext(_configuration!);
|
|
|
|
|
|
//注册通知事件服务
|
|
|
|
|
|
containerRegistry.AddNotificationEventBus();
|
|
|
|
|
|
// 服务注册
|
|
|
|
|
|
containerRegistry.AddService(_configuration!);
|
|
|
|
|
|
// 注册HttpClient连接池
|
|
|
|
|
|
containerRegistry.AddHttpClient();
|
|
|
|
|
|
|
|
|
|
|
|
// 注册断联续传同步模块
|
|
|
|
|
|
_syncModule.RegisterTypes(containerRegistry);
|
|
|
|
|
|
|
|
|
|
|
|
// 注册所有需要导航的视图
|
|
|
|
|
|
containerRegistry.AddNavigation();
|
|
|
|
|
|
}
|
|
|
|
|
|
protected override void OnInitialized()
|
|
|
|
|
|
{
|
|
|
|
|
|
base.OnInitialized();
|
|
|
|
|
|
// 获取日志服务
|
|
|
|
|
|
_logger = Container.Resolve<ILoggerService>();
|
|
|
|
|
|
// 初始化全局异常处理(通过解析触发构造函数注册)
|
|
|
|
|
|
Container.Resolve<GlobalExceptionHandler>();
|
2026-05-18 17:31:18 +08:00
|
|
|
|
// 保存默认主题(用户级 user.config;损坏时自动删文件并重载)
|
|
|
|
|
|
PersistSkinTypeUserSettingSafe();
|
2026-04-28 10:23:58 +08:00
|
|
|
|
|
|
|
|
|
|
_logger.Information("应用程序已启动");
|
|
|
|
|
|
|
2026-05-12 18:29:03 +08:00
|
|
|
|
// 加载 PrintDot 本地设置(使 PrintDotService 在任何页面调用前已有配置)
|
|
|
|
|
|
PrintDotSettings.Load();
|
|
|
|
|
|
|
2026-04-28 10:23:58 +08:00
|
|
|
|
// 启动断联续传同步模块
|
|
|
|
|
|
_syncModule.OnInitialized(Container);
|
|
|
|
|
|
}
|
|
|
|
|
|
protected override void OnExit(ExitEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
BaseViewModel.StopTokenCheckTimer();
|
|
|
|
|
|
base.OnExit(e);
|
|
|
|
|
|
_logger?.Information("应用程序已退出");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|