You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
369 lines
16 KiB
369 lines
16 KiB
using AIK.Common.Interface;
|
|
using AIK.Models.GuageModels;
|
|
using AIK.Service.Extensions;
|
|
using AIK.Service.IService;
|
|
using AIK.Service.Service;
|
|
using AIK.ViewModels;
|
|
using AIK.ViewModels.ChipWriteStart;
|
|
using AIK.ViewModels.CloudData;
|
|
using AIK.ViewModels.CommonViewModel;
|
|
using AIK.ViewModels.HomePage;
|
|
using AIK.ViewModels.Integral;
|
|
using AIK.ViewModels.Membership;
|
|
using AIK.ViewModels.RemoteControlDetector;
|
|
using AIK.ViewModels.RemoteCopy;
|
|
using AIK.ViewModels.RemoteGeneration;
|
|
using AIK.ViewModels.TirePressure;
|
|
using AIK.ViewModels.UpdateCenter;
|
|
using AIK.ViewModels.UserKeyRecord;
|
|
using AIK.Views.AccountPages;
|
|
using AIK.Views.MenuPages;
|
|
using AIK.Views.MenuPages.ChipGeneration;
|
|
using AIK.Views.MenuPages.ChipWriteStart;
|
|
using AIK.Views.MenuPages.CloudData;
|
|
using AIK.Views.MenuPages.CommonPage;
|
|
using AIK.Views.MenuPages.Home;
|
|
using AIK.Views.MenuPages.Integral;
|
|
using AIK.Views.MenuPages.RemoteControlDetector;
|
|
using AIK.Views.MenuPages.RemoteCopy;
|
|
using AIK.Views.MenuPages.RemoteGeneration;
|
|
using AIK.Views.MenuPages.TirePressure;
|
|
using AIK.Views.MenuPages.UpdateCenter;
|
|
using AIK.Views.UIServices;
|
|
using AIK.Views.UpdatePages;
|
|
using AIKKey.Infrastructure;
|
|
using AutoUpdaterDotNET;
|
|
using CommunityToolkit.Mvvm.DependencyInjection;
|
|
using CommunityToolkit.Mvvm.Messaging;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Hosting;
|
|
using Microsoft.Extensions.Logging;
|
|
using NLog;
|
|
using NLog.Extensions.Hosting;
|
|
using NLog.Extensions.Logging;
|
|
using System;
|
|
using System.Configuration;
|
|
using System.Data;
|
|
using System.Diagnostics;
|
|
using System.Net;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Windows;
|
|
using System.Windows.Navigation;
|
|
|
|
namespace AIKKey
|
|
{
|
|
/// <summary>
|
|
/// Interaction logic for App.xaml
|
|
/// </summary>
|
|
public partial class App : Application
|
|
{
|
|
private static IHost? _host;
|
|
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
|
|
private UpdateWindow _updateWindow;
|
|
|
|
public static IHost Host => _host
|
|
??= CreateHostBuilder(Environment.GetCommandLineArgs()).Build();
|
|
private static Mutex _mutex;
|
|
public static IServiceProvider Services => Host.Services;
|
|
private IServiceProvider? _serviceProvider;
|
|
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
|
Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args)
|
|
.ConfigureAppConfiguration((context, config) =>
|
|
{
|
|
config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
|
|
config.AddEnvironmentVariables();
|
|
})
|
|
.ConfigureLogging((context, logging) =>
|
|
{
|
|
logging.ClearProviders();
|
|
logging.AddNLog(context.Configuration);
|
|
})
|
|
.UseNLog()
|
|
.ConfigureServices(ConfigureServices);
|
|
|
|
private static void ConfigureServices(HostBuilderContext context, IServiceCollection services)
|
|
{
|
|
// 注册视图模型
|
|
services.AddSingleton<MainViewModel>();
|
|
services.AddTransient<RemoteCopyViewModel>();
|
|
services.AddSingleton<KeyModelViewModel>();
|
|
services.AddSingleton<RemoteGenerationViewModel>();
|
|
services.AddSingleton<RemoteControlDetectorViewModel>();
|
|
services.AddSingleton<SettingsViewModel>();
|
|
services.AddSingleton<VideoPlayerViewModel>();
|
|
services.AddSingleton<SemiCircleGaugeViewModel>();
|
|
services.AddSingleton<HomePageViewModel>();
|
|
services.AddTransient<UpdateCenterViewModel>();
|
|
services.AddTransient<UserKeyRecordViewModel>();
|
|
services.AddTransient<IntegralViewModel>();
|
|
services.AddTransient<IntegralRechargeViewModel>();
|
|
services.AddTransient<PaymentQrCodeViewModel>();
|
|
services.AddTransient<MembershipOpenViewModel>();
|
|
services.AddSingleton<CloudDataViewModel>();
|
|
services.AddSingleton<ChipWriteStartViewModel>();
|
|
services.AddSingleton<TirePressureViewModel>();
|
|
#region 注册服务
|
|
// 基础服务
|
|
services.AddSingleton<IConfigurationService, ConfigurationService>();
|
|
services.AddSingleton<ILogService, LogService>();
|
|
services.AddSingleton<IHttpClientService, HttpClientService>();
|
|
services.AddSingleton<IRetryPolicyService, RetryPolicyService>();
|
|
services.AddSingleton<IDataCacheService, SqliteCacheService>();
|
|
services.AddSingleton<IAikDataService, AikDataService>();
|
|
services.AddSingleton<IMembershipService, MembershipService>();
|
|
services.AddSingleton<IPaymentOrderService, PaymentOrderService>();
|
|
services.AddSingleton<ISettingsService, SettingsService>();
|
|
services.AddSingleton<IUserSessionCoordinator, UserSessionCoordinator>();
|
|
|
|
//更新服务
|
|
services.AddSingleton<IUpdateService, UpdateService>();
|
|
|
|
//工厂(文件操作obd/bin)
|
|
services.AddSingleton<FileOBDService>();
|
|
services.AddSingleton<FileBinService>();
|
|
services.AddSingleton<FileCloudDataService>();
|
|
services.AddSingleton<IFileServiceFactory>(sp => new FileServiceFactory(
|
|
sp.GetRequiredService<FileOBDService>(),
|
|
sp.GetRequiredService<FileBinService>(),
|
|
sp.GetRequiredService<FileCloudDataService>()
|
|
));
|
|
//文件基础服务(复制、解压)
|
|
services.AddSingleton<IFileOperationService, FileOperationService>();
|
|
//窗体弹窗
|
|
services.AddSingleton<IWindowFactory, WindowFactory>();
|
|
services.AddSingleton<IWindowService, WindowService>();
|
|
services.AddSingleton<IModalDialogService, ModalDialogService>();
|
|
//二维码
|
|
services.AddSingleton<IQRCodeService, QRCodeService>();
|
|
|
|
//业务服务
|
|
services.AddSingleton<ICarfactoryService, CarfactoryService>();
|
|
services.AddSingleton<IVehDataService, VehDataService>();
|
|
services.AddSingleton<IImageCacheService, RemoteImageCacheService>();
|
|
services.AddSingleton<ILocalizationService, LocalizationService>();
|
|
|
|
//Hid通信
|
|
services.AddSingleton<IHidCommunicationService, HidCommunicationService>();
|
|
// 文件选择
|
|
services.AddSingleton<IFileDialogService, WpfFileDialogService>();
|
|
#endregion
|
|
//注册模型
|
|
services.AddSingleton<GaugeModel>(); // 单例模式,整个应用共享一个实例
|
|
|
|
// 注册 Messenger(单例)
|
|
services.AddSingleton<IMessenger>(provider => WeakReferenceMessenger.Default);
|
|
|
|
// 注册窗口
|
|
services.AddSingleton<MainWindow>();
|
|
services.AddSingleton<RemoteCopyPage>();
|
|
services.AddSingleton<KeyModelPage>();
|
|
services.AddSingleton<RemoteGenerationPage>();
|
|
services.AddTransient<RemoteControlDetectorPage>();
|
|
services.AddSingleton<SettingsPage>();
|
|
services.AddSingleton<VideoPopupWindow>();
|
|
services.AddSingleton<SemiCircleGauge>();
|
|
services.AddSingleton<HomePage>();
|
|
services.AddTransient<UpdateCenterPage>();
|
|
services.AddTransient<IntegralRecordPage>();
|
|
services.AddSingleton<CloudDataPage>();
|
|
services.AddSingleton<ChipWriteStartPage>();
|
|
services.AddSingleton<TirePressurePage>();
|
|
// 注册 HttpClientFactory
|
|
services.AddHttpClient();
|
|
|
|
}
|
|
private void RegisterWindowMappings()
|
|
{
|
|
var windowService = Services.GetRequiredService<IWindowService>();
|
|
|
|
// 注册功能窗口映射
|
|
windowService.RegisterWindow(WindowKey.VideoPopup, typeof(VideoPopupWindow), typeof(VideoPlayerViewModel));
|
|
|
|
}
|
|
protected override async void OnStartup(StartupEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
string appName = "AIKKeyWin7_Mutex"; // 替换为你的应用名
|
|
#if CN
|
|
appName = "AIKKeyWin7_Mutex";
|
|
#elif TEST
|
|
appName = "AIKKeyTESTWin7_Mutex";
|
|
#else
|
|
appName = "AIKKeyGlobalWin7_Mutex";
|
|
#endif
|
|
|
|
|
|
_mutex = new Mutex(true, appName, out bool createdNew);
|
|
|
|
if (!createdNew)
|
|
{
|
|
MessageBox.Show("应用已在运行!");
|
|
Current.Shutdown();
|
|
return;
|
|
}
|
|
|
|
// 系统级操作(Win7 硬件重扫 CM_ReScan_DevNode 等)需要管理员权限:
|
|
// 记录权限授予是否生效,便于排查"重扫未生效/权限未开启"问题。
|
|
//Logger.Info(UsbRescanHelper.IsAdministrator()
|
|
// ? "权限授予成功:当前进程已以管理员权限运行(硬件重扫等系统操作可用)"
|
|
// : "权限未授予:当前进程非管理员权限,硬件重扫(CM_ReScan_DevNode)等系统操作将无法执行");
|
|
|
|
// Win7 下 .NET Framework 默认仅协商 TLS 1.0,而服务器(key.aik518.com)只接受 TLS 1.2+,
|
|
// 必须显式启用 TLS 1.2,否则所有 HTTPS/wss 请求(扫码登录、业务接口、图片下载)都会握手失败。
|
|
// 放在 base.OnStartup 之前:StartupUri 创建 MainWindow 时会立即发起扫码登录连接。
|
|
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12
|
|
| SecurityProtocolType.Tls11
|
|
| SecurityProtocolType.Tls;
|
|
|
|
//// 系统级兜底:写入 SchUseStrongCrypto + SChannel TLS 1.2 注册表(需要管理员权限,
|
|
//// 首次管理员运行一次后重启生效;无权限时只记日志,不阻塞启动)。
|
|
//if (TlsRegistryHelper.IsWindows7)
|
|
//{
|
|
// TlsRegistryHelper.EnsureTls12Enabled(out bool changed, out string error);
|
|
// if (changed)
|
|
// {
|
|
// Logger.Info("已写入 TLS 1.2 注册表配置(SchUseStrongCrypto + SChannel),需要重启系统后生效。");
|
|
// }
|
|
// else if (!string.IsNullOrEmpty(error))
|
|
// {
|
|
// Logger.Warn("TLS 1.2 注册表配置未写入: {Error}", error);
|
|
// }
|
|
// // 无论是否本次写入,都记录当前注册表实际状态,便于定位"配置了但未生效"的问题
|
|
// Logger.Info("TLS 1.2 注册表当前状态: {Status}", TlsRegistryHelper.GetRegistryStatus());
|
|
//}
|
|
|
|
//// Win7 启动自检:用 SslStream 直接探测 TLS 1.2 握手,区分"网络不通"与"系统 SChannel 未启用 TLS 1.2"
|
|
//_ = RunTls12StartupProbeAsync();
|
|
|
|
// 初始化全局异常处理
|
|
base.OnStartup(e);
|
|
SetupExceptionHandling();
|
|
await Host.StartAsync();
|
|
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
|
// 配置 CommunityToolkit.Mvvm 的 Ioc
|
|
Ioc.Default.ConfigureServices(Services);
|
|
IUpdateService updateService= Services.GetRequiredService<IUpdateService>();
|
|
|
|
//await updateService.StartUpdateProcess();
|
|
await updateService.CheckForUpdate();
|
|
RegisterWindowMappings();
|
|
|
|
// 后台预加热 WebView2 环境,加速 AI 客服窗口首次打开
|
|
_ = PreWarmWebView2Async();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Fatal(ex, "应用程序启动失败");
|
|
MessageBox.Show($"应用程序启动失败: {ex.Message}", "错误",
|
|
MessageBoxButton.OK, MessageBoxImage.Error);
|
|
Shutdown();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 后台预创建 WebView2 环境,避免 AI 客服窗口打开时实时初始化。
|
|
/// WebView2 运行时内部会缓存已创建的环境,后续 EnsureCoreWebView2Async 直接复用。
|
|
/// </summary>
|
|
private static async Task PreWarmWebView2Async()
|
|
{
|
|
try
|
|
{
|
|
await Task.Run(() => Microsoft.Web.WebView2.Core.CoreWebView2Environment.CreateAsync());
|
|
}
|
|
catch
|
|
{
|
|
// 预热失败不影响主流程,窗口打开时还会再尝试 EnsureCoreWebView2Async
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Win7 启动自检:对业务 API 服务器做一次 TLS 1.2 握手探测并记入日志。
|
|
/// 用于区分"网络不通"与"系统 SChannel 未启用 TLS 1.2"(后者在 Win7 上只能通过
|
|
/// 打补丁 + 写注册表 + 重启系统解决,代码层 ServicePointManager/SslProtocols 无法绕过)。
|
|
/// </summary>
|
|
private static async Task RunTls12StartupProbeAsync()
|
|
{
|
|
try
|
|
{
|
|
string host;
|
|
int port = 443;
|
|
try
|
|
{
|
|
var configService = Services.GetRequiredService<IConfigurationService>();
|
|
var baseUrl = configService.ApiSettings.BaseUrl;
|
|
var uri = new Uri(baseUrl.Contains("://") ? baseUrl : "https://" + baseUrl);
|
|
host = uri.Host;
|
|
port = uri.Port > 0 ? uri.Port : 443;
|
|
}
|
|
catch
|
|
{
|
|
host = "key.aikkey.cn"; // 兜底,与日志中实际访问的服务器一致
|
|
}
|
|
|
|
var result = await TlsRegistryHelper.ProbeTls12HandshakeAsync(host, port);
|
|
Logger.Info("TLS 1.2 启动自检: {Result}", result);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Error(ex, "TLS 1.2 启动自检失败");
|
|
}
|
|
}
|
|
|
|
protected override async void OnExit(ExitEventArgs e)
|
|
{
|
|
_mutex?.ReleaseMutex();
|
|
base.OnExit(e);
|
|
|
|
using (Host)
|
|
{
|
|
await Host.StopAsync(TimeSpan.FromSeconds(5));
|
|
}
|
|
}
|
|
|
|
private void SetupExceptionHandling()
|
|
{
|
|
// UI线程异常
|
|
DispatcherUnhandledException += (s, e) =>
|
|
{
|
|
Logger.Fatal(e.Exception, "UI线程未处理异常");
|
|
e.Handled = true;
|
|
ShowErrorDialog("应用程序错误", e.Exception);
|
|
};
|
|
|
|
// 非UI线程异常
|
|
AppDomain.CurrentDomain.UnhandledException += (s, e) =>
|
|
{
|
|
if (e.ExceptionObject is Exception ex)
|
|
{
|
|
Logger.Fatal(ex, "非UI线程未处理异常");
|
|
}
|
|
};
|
|
|
|
// Task异常
|
|
TaskScheduler.UnobservedTaskException += (s, e) =>
|
|
{
|
|
Logger.Error(e.Exception, "Task未观察异常");
|
|
e.SetObserved();
|
|
};
|
|
}
|
|
|
|
private void ShowErrorDialog(string title, Exception ex)
|
|
{
|
|
// 使用ModernWPF或自定义对话框
|
|
MessageBox.Show($"{title}\n\n{ex.Message}", "错误",
|
|
MessageBoxButton.OK, MessageBoxImage.Error);
|
|
}
|
|
|
|
public static void ConfigureServices(IServiceCollection services)
|
|
{
|
|
// 这个方法可以用于外部配置
|
|
ConfigureServices(null!, services);
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|