A:调整部分日志;增加Client对于依赖注入的支持
This commit is contained in:
1 parent
2739a71134
commit
e1d059ed29
12 files changed
+143
-39
No files matched your search
@@ -10,3 +10,7 @@
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,9 @@ using System;
|
||||
using System.Windows;
|
||||
using Client.Services;
|
||||
using Client.ViewModels;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Common;
|
||||
using Config;
|
||||
@@ -10,6 +12,7 @@ using NetClient.DependencyInjection;
|
||||
using Sharp.Infrastructure.Configuration;
|
||||
using Sharp.Log.Extensions;
|
||||
using Sharp.NetServer.DependencyInjection;
|
||||
using Sharp.Wpf.Exceptions;
|
||||
using Sharp.Wpf.Services;
|
||||
|
||||
namespace Client
|
||||
@@ -24,7 +27,7 @@ namespace Client
|
||||
protected override void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
base.OnStartup(e);
|
||||
|
||||
|
||||
// 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);
|
||||
|
||||
@@ -33,6 +36,7 @@ namespace Client
|
||||
|
||||
// ConfigManager and typed SystemConfig accessor
|
||||
services.AddSingleton(cm);
|
||||
services.TryAddSingleton<IConfiguration>(cm.Configuration); // Register IConfiguration for BindConfiguration
|
||||
services.AddSystemConfig(cm);
|
||||
|
||||
// Logging (binds from section "SharpLog")
|
||||
@@ -42,6 +46,9 @@ namespace Client
|
||||
services.AddNetClient(cm);
|
||||
services.AddNetServer(cm);
|
||||
|
||||
// WPF exception handler
|
||||
services.AddSingleton<IUiExceptionHandler, UiExceptionHandler>();
|
||||
|
||||
// WPF app services
|
||||
services.AddSingleton<IDialogService, DialogService>();
|
||||
services.AddSingleton<IAuthenticationService, DemoAuthenticationService>();
|
||||
@@ -54,22 +61,32 @@ namespace Client
|
||||
|
||||
_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(cm);
|
||||
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 loginResult = loginWindow.ShowDialog();
|
||||
|
||||
logger.LogInformation("Login window closed with result: {LoginResult}", loginResult);
|
||||
|
||||
if (loginResult == true)
|
||||
{
|
||||
logger.LogInformation("Login successful, showing main window");
|
||||
var main = _serviceProvider.GetRequiredService<MainWindow>();
|
||||
main.Show();
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogInformation("Login cancelled or failed, shutting down application");
|
||||
Shutdown();
|
||||
}
|
||||
}
|
||||
@@ -80,4 +97,4 @@ namespace Client
|
||||
base.OnExit(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,27 @@
|
||||
using System;
|
||||
using HandyControl.Controls;
|
||||
using Client.ViewModels;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Client;
|
||||
|
||||
public partial class LoginWindow : GlowWindow
|
||||
{
|
||||
private readonly LoginViewModel _viewModel;
|
||||
private readonly ILogger<LoginWindow>? _logger;
|
||||
|
||||
public LoginWindow(LoginViewModel viewModel)
|
||||
public LoginWindow(LoginViewModel viewModel, ILogger<LoginWindow>? logger = null)
|
||||
{
|
||||
InitializeComponent();
|
||||
_viewModel = viewModel;
|
||||
_logger = logger;
|
||||
DataContext = viewModel;
|
||||
_viewModel.LoginSucceeded += HandleLoginSucceeded;
|
||||
}
|
||||
|
||||
private void HandleLoginSucceeded(object? sender, EventArgs e)
|
||||
{
|
||||
_logger?.LogInformation("HandleLoginSucceeded called, setting DialogResult = true");
|
||||
DialogResult = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Client.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Sharp.Wpf.Commands;
|
||||
using Sharp.Wpf.Mvvm;
|
||||
|
||||
@@ -10,6 +11,7 @@ public sealed class LoginViewModel : ViewModelBase
|
||||
{
|
||||
private readonly IAuthenticationService _authenticationService;
|
||||
private readonly IUserSession _userSession;
|
||||
private readonly ILogger<LoginViewModel> _logger;
|
||||
private readonly IAsyncCommand _loginCommand;
|
||||
|
||||
private string _userName = "admin";
|
||||
@@ -18,10 +20,11 @@ public sealed class LoginViewModel : ViewModelBase
|
||||
|
||||
public event EventHandler? LoginSucceeded;
|
||||
|
||||
public LoginViewModel(IAuthenticationService authenticationService, IUserSession userSession)
|
||||
public LoginViewModel(IAuthenticationService authenticationService, IUserSession userSession, ILogger<LoginViewModel> logger)
|
||||
{
|
||||
_authenticationService = authenticationService;
|
||||
_userSession = userSession;
|
||||
_logger = logger;
|
||||
_loginCommand = new AsyncCommand(LoginAsync);
|
||||
}
|
||||
|
||||
@@ -67,20 +70,29 @@ public sealed class LoginViewModel : ViewModelBase
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Login attempt for user: {UserName}", UserName);
|
||||
IsBusy = true;
|
||||
ErrorMessage = null;
|
||||
|
||||
var result = await _authenticationService.LoginAsync(UserName, Password).ConfigureAwait(false);
|
||||
var result = await _authenticationService.LoginAsync(UserName, Password);
|
||||
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
_logger.LogWarning("Login failed for user: {UserName}, Error: {Error}", UserName, result.Error);
|
||||
ErrorMessage = result.Error;
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Login succeeded for user: {UserName}", UserName);
|
||||
_userSession.SignIn(UserName, result.DisplayName);
|
||||
_logger.LogInformation("Invoking LoginSucceeded event");
|
||||
LoginSucceeded?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Login failed for user: {UserName}", UserName);
|
||||
ErrorMessage = "An unexpected error occurred.";
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsBusy = false;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Data;
|
||||
using System.Data.OleDb;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Sharp.Data.Access.Connections;
|
||||
@@ -223,7 +224,11 @@ public class AccessDataService : IAccessDataService
|
||||
|
||||
using var command = new OleDbCommand(sql, connection);
|
||||
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>();
|
||||
using var reader = command.ExecuteReader();
|
||||
@@ -250,7 +255,11 @@ public class AccessDataService : IAccessDataService
|
||||
|
||||
using var command = new OleDbCommand(sql, connection);
|
||||
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();
|
||||
|
||||
@@ -274,7 +283,11 @@ public class AccessDataService : IAccessDataService
|
||||
|
||||
using var command = new OleDbCommand(sql, connection);
|
||||
if (parameters.Length > 0)
|
||||
command.Parameters.AddRange(parameters);
|
||||
{
|
||||
// 克隆参数以避免重试时的"已包含在另一个集合中"错误
|
||||
var clonedParams = parameters.Select(p => CloneParameter(p)).ToArray();
|
||||
command.Parameters.AddRange(clonedParams);
|
||||
}
|
||||
|
||||
var result = command.ExecuteScalar();
|
||||
_logger?.LogDebug("标量查询返回: {Result}", result);
|
||||
@@ -293,7 +306,11 @@ public class AccessDataService : IAccessDataService
|
||||
|
||||
using var command = new OleDbCommand(sql, connection);
|
||||
if (parameters.Length > 0)
|
||||
command.Parameters.AddRange(parameters);
|
||||
{
|
||||
// 克隆参数以避免重试时的"已包含在另一个集合中"错误
|
||||
var clonedParams = parameters.Select(p => CloneParameter(p)).ToArray();
|
||||
command.Parameters.AddRange(clonedParams);
|
||||
}
|
||||
|
||||
var rowsAffected = command.ExecuteNonQuery();
|
||||
_logger?.LogDebug("命令影响了 {RowsAffected} 行", rowsAffected);
|
||||
@@ -403,6 +420,27 @@ public class AccessDataService : IAccessDataService
|
||||
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()
|
||||
{
|
||||
var obj = new T();
|
||||
|
||||
@@ -294,9 +294,20 @@ public class HttpServer
|
||||
var controller = _serviceProvider.GetService(controllerType);
|
||||
if (controller != null)
|
||||
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)!;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,13 +23,13 @@ namespace Server.Controllers
|
||||
public async Task GetAll(HttpContext context)
|
||||
{
|
||||
var items = _dataStore.Values.ToList();
|
||||
var result = ResultExtensions.HttpSuccess(new
|
||||
var data = new
|
||||
{
|
||||
Count = items.Count,
|
||||
Items = items
|
||||
});
|
||||
};
|
||||
|
||||
await WriteSuccessResponseAsync(context, result);
|
||||
await WriteSuccessResponseAsync(context, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -49,8 +49,7 @@ namespace Server.Controllers
|
||||
|
||||
if (_dataStore.TryGetValue(id, out var item))
|
||||
{
|
||||
var result = ResultExtensions.HttpSuccess(item);
|
||||
await WriteSuccessResponseAsync(context, result);
|
||||
await WriteSuccessResponseAsync(context, item);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -87,8 +86,7 @@ namespace Server.Controllers
|
||||
|
||||
_dataStore[id] = item;
|
||||
|
||||
var result = ResultExtensions.HttpSuccess(item, "Data created successfully");
|
||||
await WriteSuccessResponseAsync(context, result);
|
||||
await WriteSuccessResponseAsync(context, item, "Data created successfully");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -129,8 +127,7 @@ namespace Server.Controllers
|
||||
item.UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
var result = ResultExtensions.HttpSuccess(item, "Data updated successfully");
|
||||
await WriteSuccessResponseAsync(context, result);
|
||||
await WriteSuccessResponseAsync(context, item, "Data updated successfully");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -150,8 +147,7 @@ namespace Server.Controllers
|
||||
|
||||
if (_dataStore.TryRemove(id, out var item))
|
||||
{
|
||||
var result = ResultExtensions.HttpSuccess(item, "Data deleted successfully");
|
||||
await WriteSuccessResponseAsync(context, result);
|
||||
await WriteSuccessResponseAsync(context, item, "Data deleted successfully");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -169,8 +165,8 @@ namespace Server.Controllers
|
||||
var count = _dataStore.Count;
|
||||
_dataStore.Clear();
|
||||
|
||||
var result = ResultExtensions.HttpSuccess(new { DeletedCount = count }, "All data cleared");
|
||||
await WriteSuccessResponseAsync(context, result);
|
||||
var data = new { DeletedCount = count };
|
||||
await WriteSuccessResponseAsync(context, data, "All data cleared");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,14 +19,14 @@ namespace Server.Controllers
|
||||
[HttpGet]
|
||||
public async Task Check(HttpContext context)
|
||||
{
|
||||
var result = ResultExtensions.HttpSuccess(new
|
||||
var data = new
|
||||
{
|
||||
Status = "Healthy",
|
||||
Timestamp = DateTime.UtcNow,
|
||||
Uptime = DateTime.UtcNow - Process.GetCurrentProcess().StartTime
|
||||
});
|
||||
};
|
||||
|
||||
await WriteSuccessResponseAsync(context, result);
|
||||
await WriteSuccessResponseAsync(context, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -37,7 +37,7 @@ namespace Server.Controllers
|
||||
public async Task DetailedCheck(HttpContext context)
|
||||
{
|
||||
var process = Process.GetCurrentProcess();
|
||||
var result = ResultExtensions.HttpSuccess(new
|
||||
var data = new
|
||||
{
|
||||
Status = "Healthy",
|
||||
Timestamp = DateTime.UtcNow,
|
||||
@@ -60,9 +60,9 @@ namespace Server.Controllers
|
||||
},
|
||||
Threads = process.Threads.Count
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
await WriteSuccessResponseAsync(context, result);
|
||||
await WriteSuccessResponseAsync(context, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Server.Controllers
|
||||
[HttpGet("/info")]
|
||||
public async Task GetSystemInfo(HttpContext context)
|
||||
{
|
||||
var result = ResultExtensions.HttpSuccess(new
|
||||
var data = new
|
||||
{
|
||||
MachineName = Environment.MachineName,
|
||||
UserName = Environment.UserName,
|
||||
@@ -30,9 +30,9 @@ namespace Server.Controllers
|
||||
CurrentDirectory = Environment.CurrentDirectory,
|
||||
TickCount = Environment.TickCount64,
|
||||
Version = Environment.Version.ToString()
|
||||
});
|
||||
};
|
||||
|
||||
await WriteSuccessResponseAsync(context, result);
|
||||
await WriteSuccessResponseAsync(context, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -59,7 +59,7 @@ namespace Server.Controllers
|
||||
return;
|
||||
}
|
||||
|
||||
var successResult = ResultExtensions.HttpSuccess(new { Name = name, Value = value });
|
||||
var successResult = new { Name = name, Value = value };
|
||||
await WriteSuccessResponseAsync(context, successResult);
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ namespace Server.Controllers
|
||||
var now = DateTime.Now;
|
||||
var utcNow = DateTime.UtcNow;
|
||||
|
||||
var result = ResultExtensions.HttpSuccess(new
|
||||
var data = new
|
||||
{
|
||||
Local = new
|
||||
{
|
||||
@@ -89,9 +89,9 @@ namespace Server.Controllers
|
||||
Time = utcNow.TimeOfDay,
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Custom logger hook.
|
||||
/// 日志钩子.
|
||||
/// </summary>
|
||||
public Action<Exception>? Log { get; set; }
|
||||
= ex => System.Diagnostics.Debug.WriteLine(ex.ToString());
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Sharp.Wpf.Exceptions;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using WpfMessageBox = System.Windows.MessageBox;
|
||||
|
||||
@@ -35,6 +36,10 @@ public sealed class UiExceptionHandler : IUiExceptionHandler
|
||||
private void OnDispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
|
||||
{
|
||||
Handle(e.Exception);
|
||||
if (CanRecover(e.Exception))
|
||||
{
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user