A:调整部分日志;增加Client对于依赖注入的支持

This commit is contained in:
jimmy committed 2025-11-20 16:35:48 +08:00
1 parent 2739a71134
commit e1d059ed29
12 files changed
+143 -39

No files matched your search

+4
View File
@@ -10,3 +10,7 @@
</ResourceDictionary> </ResourceDictionary>
</Application.Resources> </Application.Resources>
</Application> </Application>
+21 -4
View File
@@ -2,7 +2,9 @@ using System;
using System.Windows; using System.Windows;
using Client.Services; using Client.Services;
using Client.ViewModels; using Client.ViewModels;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Common; using Common;
using Config; using Config;
@@ -10,6 +12,7 @@ using NetClient.DependencyInjection;
using Sharp.Infrastructure.Configuration; using Sharp.Infrastructure.Configuration;
using Sharp.Log.Extensions; using Sharp.Log.Extensions;
using Sharp.NetServer.DependencyInjection; using Sharp.NetServer.DependencyInjection;
using Sharp.Wpf.Exceptions;
using Sharp.Wpf.Services; using Sharp.Wpf.Services;
namespace Client namespace Client
@@ -24,7 +27,7 @@ namespace Client
protected override void OnStartup(StartupEventArgs e) protected override void OnStartup(StartupEventArgs e)
{ {
base.OnStartup(e); base.OnStartup(e);
// 1) Build configuration manager from appsettings.json (+ reload on change + env vars) // 1) Build configuration manager from appsettings.json (+ reload on change + env vars)
var cm = ConfigBuilder.BuildFromFiles(new[] { "appsettings.json" }, reloadOnChange: true, includeEnvironment: true, basePath: AppContext.BaseDirectory); var cm = ConfigBuilder.BuildFromFiles(new[] { "appsettings.json" }, reloadOnChange: true, includeEnvironment: true, basePath: AppContext.BaseDirectory);
@@ -33,6 +36,7 @@ namespace Client
// ConfigManager and typed SystemConfig accessor // ConfigManager and typed SystemConfig accessor
services.AddSingleton(cm); services.AddSingleton(cm);
services.TryAddSingleton<IConfiguration>(cm.Configuration); // Register IConfiguration for BindConfiguration
services.AddSystemConfig(cm); services.AddSystemConfig(cm);
// Logging (binds from section "SharpLog") // Logging (binds from section "SharpLog")
@@ -42,6 +46,9 @@ namespace Client
services.AddNetClient(cm); services.AddNetClient(cm);
services.AddNetServer(cm); services.AddNetServer(cm);
// WPF exception handler
services.AddSingleton<IUiExceptionHandler, UiExceptionHandler>();
// WPF app services // WPF app services
services.AddSingleton<IDialogService, DialogService>(); services.AddSingleton<IDialogService, DialogService>();
services.AddSingleton<IAuthenticationService, DemoAuthenticationService>(); services.AddSingleton<IAuthenticationService, DemoAuthenticationService>();
@@ -54,22 +61,32 @@ namespace Client
_serviceProvider = services.BuildServiceProvider(); _serviceProvider = services.BuildServiceProvider();
// 3) Register global facades for convenience // 3) Activate global WPF exception handler
var exceptionHandler = _serviceProvider.GetRequiredService<IUiExceptionHandler>();
exceptionHandler.Register();
// 4) Register global facades for convenience
Global.RegisterSingleton(_serviceProvider); Global.RegisterSingleton(_serviceProvider);
Global.RegisterSingleton(cm); Global.RegisterSingleton(cm);
Global.RegisterSingleton(_serviceProvider.GetRequiredService<ISystemConfigAccessor>()); Global.RegisterSingleton(_serviceProvider.GetRequiredService<ISystemConfigAccessor>());
// 4) Show login window first, then main shell if authenticated // 5) Show login window first, then main shell if authenticated
var logger = _serviceProvider.GetRequiredService<ILogger<App>>();
logger.LogInformation("Showing login window");
var loginWindow = _serviceProvider.GetRequiredService<LoginWindow>(); var loginWindow = _serviceProvider.GetRequiredService<LoginWindow>();
var loginResult = loginWindow.ShowDialog(); var loginResult = loginWindow.ShowDialog();
logger.LogInformation("Login window closed with result: {LoginResult}", loginResult);
if (loginResult == true) if (loginResult == true)
{ {
logger.LogInformation("Login successful, showing main window");
var main = _serviceProvider.GetRequiredService<MainWindow>(); var main = _serviceProvider.GetRequiredService<MainWindow>();
main.Show(); main.Show();
} }
else else
{ {
logger.LogInformation("Login cancelled or failed, shutting down application");
Shutdown(); Shutdown();
} }
} }
@@ -80,4 +97,4 @@ namespace Client
base.OnExit(e); base.OnExit(e);
} }
} }
} }
+5 -1
View File
@@ -1,23 +1,27 @@
using System; using System;
using HandyControl.Controls; using HandyControl.Controls;
using Client.ViewModels; using Client.ViewModels;
using Microsoft.Extensions.Logging;
namespace Client; namespace Client;
public partial class LoginWindow : GlowWindow public partial class LoginWindow : GlowWindow
{ {
private readonly LoginViewModel _viewModel; private readonly LoginViewModel _viewModel;
private readonly ILogger<LoginWindow>? _logger;
public LoginWindow(LoginViewModel viewModel) public LoginWindow(LoginViewModel viewModel, ILogger<LoginWindow>? logger = null)
{ {
InitializeComponent(); InitializeComponent();
_viewModel = viewModel; _viewModel = viewModel;
_logger = logger;
DataContext = viewModel; DataContext = viewModel;
_viewModel.LoginSucceeded += HandleLoginSucceeded; _viewModel.LoginSucceeded += HandleLoginSucceeded;
} }
private void HandleLoginSucceeded(object? sender, EventArgs e) private void HandleLoginSucceeded(object? sender, EventArgs e)
{ {
_logger?.LogInformation("HandleLoginSucceeded called, setting DialogResult = true");
DialogResult = true; DialogResult = true;
} }
+14 -2
View File
@@ -1,6 +1,7 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Client.Services; using Client.Services;
using Microsoft.Extensions.Logging;
using Sharp.Wpf.Commands; using Sharp.Wpf.Commands;
using Sharp.Wpf.Mvvm; using Sharp.Wpf.Mvvm;
@@ -10,6 +11,7 @@ public sealed class LoginViewModel : ViewModelBase
{ {
private readonly IAuthenticationService _authenticationService; private readonly IAuthenticationService _authenticationService;
private readonly IUserSession _userSession; private readonly IUserSession _userSession;
private readonly ILogger<LoginViewModel> _logger;
private readonly IAsyncCommand _loginCommand; private readonly IAsyncCommand _loginCommand;
private string _userName = "admin"; private string _userName = "admin";
@@ -18,10 +20,11 @@ public sealed class LoginViewModel : ViewModelBase
public event EventHandler? LoginSucceeded; public event EventHandler? LoginSucceeded;
public LoginViewModel(IAuthenticationService authenticationService, IUserSession userSession) public LoginViewModel(IAuthenticationService authenticationService, IUserSession userSession, ILogger<LoginViewModel> logger)
{ {
_authenticationService = authenticationService; _authenticationService = authenticationService;
_userSession = userSession; _userSession = userSession;
_logger = logger;
_loginCommand = new AsyncCommand(LoginAsync); _loginCommand = new AsyncCommand(LoginAsync);
} }
@@ -67,20 +70,29 @@ public sealed class LoginViewModel : ViewModelBase
{ {
try try
{ {
_logger.LogInformation("Login attempt for user: {UserName}", UserName);
IsBusy = true; IsBusy = true;
ErrorMessage = null; ErrorMessage = null;
var result = await _authenticationService.LoginAsync(UserName, Password).ConfigureAwait(false); var result = await _authenticationService.LoginAsync(UserName, Password);
if (!result.Succeeded) if (!result.Succeeded)
{ {
_logger.LogWarning("Login failed for user: {UserName}, Error: {Error}", UserName, result.Error);
ErrorMessage = result.Error; ErrorMessage = result.Error;
return; return;
} }
_logger.LogInformation("Login succeeded for user: {UserName}", UserName);
_userSession.SignIn(UserName, result.DisplayName); _userSession.SignIn(UserName, result.DisplayName);
_logger.LogInformation("Invoking LoginSucceeded event");
LoginSucceeded?.Invoke(this, EventArgs.Empty); LoginSucceeded?.Invoke(this, EventArgs.Empty);
} }
catch (Exception ex)
{
_logger.LogError(ex, "Login failed for user: {UserName}", UserName);
ErrorMessage = "An unexpected error occurred.";
}
finally finally
{ {
IsBusy = false; IsBusy = false;
+42 -4
View File
@@ -1,5 +1,6 @@
using System.Data; using System.Data;
using System.Data.OleDb; using System.Data.OleDb;
using System.Linq;
using System.Reflection; using System.Reflection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Sharp.Data.Access.Connections; using Sharp.Data.Access.Connections;
@@ -223,7 +224,11 @@ public class AccessDataService : IAccessDataService
using var command = new OleDbCommand(sql, connection); using var command = new OleDbCommand(sql, connection);
if (parameters.Length > 0) if (parameters.Length > 0)
command.Parameters.AddRange(parameters); {
// 克隆参数以避免重试时的"已包含在另一个集合中"错误
var clonedParams = parameters.Select(p => CloneParameter(p)).ToArray();
command.Parameters.AddRange(clonedParams);
}
var results = new List<T>(); var results = new List<T>();
using var reader = command.ExecuteReader(); using var reader = command.ExecuteReader();
@@ -250,7 +255,11 @@ public class AccessDataService : IAccessDataService
using var command = new OleDbCommand(sql, connection); using var command = new OleDbCommand(sql, connection);
if (parameters.Length > 0) if (parameters.Length > 0)
command.Parameters.AddRange(parameters); {
// 克隆参数以避免重试时的"已包含在另一个集合中"错误
var clonedParams = parameters.Select(p => CloneParameter(p)).ToArray();
command.Parameters.AddRange(clonedParams);
}
using var reader = command.ExecuteReader(); using var reader = command.ExecuteReader();
@@ -274,7 +283,11 @@ public class AccessDataService : IAccessDataService
using var command = new OleDbCommand(sql, connection); using var command = new OleDbCommand(sql, connection);
if (parameters.Length > 0) if (parameters.Length > 0)
command.Parameters.AddRange(parameters); {
// 克隆参数以避免重试时的"已包含在另一个集合中"错误
var clonedParams = parameters.Select(p => CloneParameter(p)).ToArray();
command.Parameters.AddRange(clonedParams);
}
var result = command.ExecuteScalar(); var result = command.ExecuteScalar();
_logger?.LogDebug("标量查询返回: {Result}", result); _logger?.LogDebug("标量查询返回: {Result}", result);
@@ -293,7 +306,11 @@ public class AccessDataService : IAccessDataService
using var command = new OleDbCommand(sql, connection); using var command = new OleDbCommand(sql, connection);
if (parameters.Length > 0) if (parameters.Length > 0)
command.Parameters.AddRange(parameters); {
// 克隆参数以避免重试时的"已包含在另一个集合中"错误
var clonedParams = parameters.Select(p => CloneParameter(p)).ToArray();
command.Parameters.AddRange(clonedParams);
}
var rowsAffected = command.ExecuteNonQuery(); var rowsAffected = command.ExecuteNonQuery();
_logger?.LogDebug("命令影响了 {RowsAffected} 行", rowsAffected); _logger?.LogDebug("命令影响了 {RowsAffected} 行", rowsAffected);
@@ -403,6 +420,27 @@ public class AccessDataService : IAccessDataService
return ex.ErrorCode == -2147467259 || ex.ErrorCode == -2147217865; return ex.ErrorCode == -2147467259 || ex.ErrorCode == -2147217865;
} }
/// <summary>
/// 克隆 OleDbParameter 以避免重试时参数被重复添加的问题
/// </summary>
private static OleDbParameter CloneParameter(OleDbParameter original)
{
var clone = new OleDbParameter
{
ParameterName = original.ParameterName,
Value = original.Value,
OleDbType = original.OleDbType,
Size = original.Size,
Direction = original.Direction,
IsNullable = original.IsNullable,
Precision = original.Precision,
Scale = original.Scale,
SourceColumn = original.SourceColumn,
SourceVersion = original.SourceVersion
};
return clone;
}
private static T MapReaderToObject<T>(IDataReader reader) where T : new() private static T MapReaderToObject<T>(IDataReader reader) where T : new()
{ {
var obj = new T(); var obj = new T();
+12 -1
View File
@@ -294,9 +294,20 @@ public class HttpServer
var controller = _serviceProvider.GetService(controllerType); var controller = _serviceProvider.GetService(controllerType);
if (controller != null) if (controller != null)
return controller; return controller;
// 使用 ActivatorUtilities 创建实例(可以解析构造函数参数)
try
{
return ActivatorUtilities.CreateInstance(_serviceProvider, controllerType);
}
catch (Exception ex)
{
_logger?.LogError(ex, "Failed to create controller {ControllerType} with DI", controllerType.Name);
throw;
}
} }
// 使用激活器创建 // 没有 DI 容器时,使用默认激活器创建(需要无参构造)
return Activator.CreateInstance(controllerType)!; return Activator.CreateInstance(controllerType)!;
} }
+9 -13
View File
@@ -23,13 +23,13 @@ namespace Server.Controllers
public async Task GetAll(HttpContext context) public async Task GetAll(HttpContext context)
{ {
var items = _dataStore.Values.ToList(); var items = _dataStore.Values.ToList();
var result = ResultExtensions.HttpSuccess(new var data = new
{ {
Count = items.Count, Count = items.Count,
Items = items Items = items
}); };
await WriteSuccessResponseAsync(context, result); await WriteSuccessResponseAsync(context, data);
} }
/// <summary> /// <summary>
@@ -49,8 +49,7 @@ namespace Server.Controllers
if (_dataStore.TryGetValue(id, out var item)) if (_dataStore.TryGetValue(id, out var item))
{ {
var result = ResultExtensions.HttpSuccess(item); await WriteSuccessResponseAsync(context, item);
await WriteSuccessResponseAsync(context, result);
} }
else else
{ {
@@ -87,8 +86,7 @@ namespace Server.Controllers
_dataStore[id] = item; _dataStore[id] = item;
var result = ResultExtensions.HttpSuccess(item, "Data created successfully"); await WriteSuccessResponseAsync(context, item, "Data created successfully");
await WriteSuccessResponseAsync(context, result);
} }
/// <summary> /// <summary>
@@ -129,8 +127,7 @@ namespace Server.Controllers
item.UpdatedAt = DateTime.UtcNow; item.UpdatedAt = DateTime.UtcNow;
} }
var result = ResultExtensions.HttpSuccess(item, "Data updated successfully"); await WriteSuccessResponseAsync(context, item, "Data updated successfully");
await WriteSuccessResponseAsync(context, result);
} }
/// <summary> /// <summary>
@@ -150,8 +147,7 @@ namespace Server.Controllers
if (_dataStore.TryRemove(id, out var item)) if (_dataStore.TryRemove(id, out var item))
{ {
var result = ResultExtensions.HttpSuccess(item, "Data deleted successfully"); await WriteSuccessResponseAsync(context, item, "Data deleted successfully");
await WriteSuccessResponseAsync(context, result);
} }
else else
{ {
@@ -169,8 +165,8 @@ namespace Server.Controllers
var count = _dataStore.Count; var count = _dataStore.Count;
_dataStore.Clear(); _dataStore.Clear();
var result = ResultExtensions.HttpSuccess(new { DeletedCount = count }, "All data cleared"); var data = new { DeletedCount = count };
await WriteSuccessResponseAsync(context, result); await WriteSuccessResponseAsync(context, data, "All data cleared");
} }
} }
@@ -19,14 +19,14 @@ namespace Server.Controllers
[HttpGet] [HttpGet]
public async Task Check(HttpContext context) public async Task Check(HttpContext context)
{ {
var result = ResultExtensions.HttpSuccess(new var data = new
{ {
Status = "Healthy", Status = "Healthy",
Timestamp = DateTime.UtcNow, Timestamp = DateTime.UtcNow,
Uptime = DateTime.UtcNow - Process.GetCurrentProcess().StartTime Uptime = DateTime.UtcNow - Process.GetCurrentProcess().StartTime
}); };
await WriteSuccessResponseAsync(context, result); await WriteSuccessResponseAsync(context, data);
} }
/// <summary> /// <summary>
@@ -37,7 +37,7 @@ namespace Server.Controllers
public async Task DetailedCheck(HttpContext context) public async Task DetailedCheck(HttpContext context)
{ {
var process = Process.GetCurrentProcess(); var process = Process.GetCurrentProcess();
var result = ResultExtensions.HttpSuccess(new var data = new
{ {
Status = "Healthy", Status = "Healthy",
Timestamp = DateTime.UtcNow, Timestamp = DateTime.UtcNow,
@@ -60,9 +60,9 @@ namespace Server.Controllers
}, },
Threads = process.Threads.Count Threads = process.Threads.Count
} }
}); };
await WriteSuccessResponseAsync(context, result); await WriteSuccessResponseAsync(context, data);
} }
} }
} }
@@ -18,7 +18,7 @@ namespace Server.Controllers
[HttpGet("/info")] [HttpGet("/info")]
public async Task GetSystemInfo(HttpContext context) public async Task GetSystemInfo(HttpContext context)
{ {
var result = ResultExtensions.HttpSuccess(new var data = new
{ {
MachineName = Environment.MachineName, MachineName = Environment.MachineName,
UserName = Environment.UserName, UserName = Environment.UserName,
@@ -30,9 +30,9 @@ namespace Server.Controllers
CurrentDirectory = Environment.CurrentDirectory, CurrentDirectory = Environment.CurrentDirectory,
TickCount = Environment.TickCount64, TickCount = Environment.TickCount64,
Version = Environment.Version.ToString() Version = Environment.Version.ToString()
}); };
await WriteSuccessResponseAsync(context, result); await WriteSuccessResponseAsync(context, data);
} }
/// <summary> /// <summary>
@@ -59,7 +59,7 @@ namespace Server.Controllers
return; return;
} }
var successResult = ResultExtensions.HttpSuccess(new { Name = name, Value = value }); var successResult = new { Name = name, Value = value };
await WriteSuccessResponseAsync(context, successResult); await WriteSuccessResponseAsync(context, successResult);
} }
@@ -73,7 +73,7 @@ namespace Server.Controllers
var now = DateTime.Now; var now = DateTime.Now;
var utcNow = DateTime.UtcNow; var utcNow = DateTime.UtcNow;
var result = ResultExtensions.HttpSuccess(new var data = new
{ {
Local = new Local = new
{ {
@@ -89,9 +89,9 @@ namespace Server.Controllers
Time = utcNow.TimeOfDay, Time = utcNow.TimeOfDay,
UnixTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds() UnixTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
} }
}); };
await WriteSuccessResponseAsync(context, result); await WriteSuccessResponseAsync(context, data);
} }
} }
} }
@@ -15,7 +15,7 @@ public sealed class ExceptionHandlingOptions
public bool RethrowAfterHandle { get; set; } = false; public bool RethrowAfterHandle { get; set; } = false;
/// <summary> /// <summary>
/// Custom logger hook. /// 日志钩子.
/// </summary> /// </summary>
public Action<Exception>? Log { get; set; } public Action<Exception>? Log { get; set; }
= ex => System.Diagnostics.Debug.WriteLine(ex.ToString()); = ex => System.Diagnostics.Debug.WriteLine(ex.ToString());
@@ -1,4 +1,5 @@
using System; using System;
using System.Net.Http;
namespace Sharp.Wpf.Exceptions; namespace Sharp.Wpf.Exceptions;
@@ -1,4 +1,5 @@
using System; using System;
using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using WpfMessageBox = System.Windows.MessageBox; using WpfMessageBox = System.Windows.MessageBox;
@@ -35,6 +36,10 @@ public sealed class UiExceptionHandler : IUiExceptionHandler
private void OnDispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e) private void OnDispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
{ {
Handle(e.Exception); Handle(e.Exception);
if (CanRecover(e.Exception))
{
e.Handled = true;
}
e.Handled = true; e.Handled = true;
} }
@@ -86,4 +91,20 @@ public sealed class UiExceptionHandler : IUiExceptionHandler
} }
} }
} }
protected bool CanRecover(Exception exception)
{
return exception switch
{
ArgumentException => true,
InvalidOperationException => true,
HttpRequestException => true,
TimeoutException => true,
OutOfMemoryException => false,
StackOverflowException => false,
AccessViolationException => false,
_ => true
};
}
} }