Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e1d059ed29 | ||
|
|
2739a71134 |
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;
|
||||
|
||||
@@ -3,6 +3,11 @@ using System.Data.OleDb;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Sharp.Data.Access.Connections;
|
||||
using Sharp.Data.Access.Initialization;
|
||||
using Sharp.Data.Access.Internal;
|
||||
using Sharp.Data.Access.Options;
|
||||
using Sharp.Data.Access.Security;
|
||||
|
||||
namespace Sharp.Data.Access;
|
||||
|
||||
@@ -14,26 +19,27 @@ internal class AccessTransaction : IAccessTransaction
|
||||
private readonly OleDbConnection _connection;
|
||||
private readonly OleDbTransaction _transaction;
|
||||
private readonly ILogger? _logger;
|
||||
private readonly bool _ownsConnection;
|
||||
private bool _disposed;
|
||||
|
||||
public AccessTransaction(OleDbConnection connection, ILogger? logger)
|
||||
public AccessTransaction(OleDbConnection connection, bool ownsConnection, ILogger? logger)
|
||||
{
|
||||
_connection = connection;
|
||||
_ownsConnection = ownsConnection;
|
||||
_transaction = connection.BeginTransaction();
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Task<int> ExecuteAsync(string sql, params OleDbParameter[] parameters)
|
||||
public int Execute(string sql, params OleDbParameter[] parameters)
|
||||
{
|
||||
using var command = new OleDbCommand(sql, _connection, _transaction);
|
||||
if (parameters.Length > 0)
|
||||
command.Parameters.AddRange(parameters);
|
||||
|
||||
var result = command.ExecuteNonQuery();
|
||||
return Task.FromResult(result);
|
||||
return command.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
public Task<List<T>> QueryAsync<T>(string sql, params OleDbParameter[] parameters) where T : new()
|
||||
public List<T> Query<T>(string sql, params OleDbParameter[] parameters) where T : new()
|
||||
{
|
||||
using var command = new OleDbCommand(sql, _connection, _transaction);
|
||||
if (parameters.Length > 0)
|
||||
@@ -48,17 +54,16 @@ internal class AccessTransaction : IAccessTransaction
|
||||
results.Add(item);
|
||||
}
|
||||
|
||||
return Task.FromResult(results);
|
||||
return results;
|
||||
}
|
||||
|
||||
public Task<object?> ExecuteScalarAsync(string sql, params OleDbParameter[] parameters)
|
||||
public object? ExecuteScalar(string sql, params OleDbParameter[] parameters)
|
||||
{
|
||||
using var command = new OleDbCommand(sql, _connection, _transaction);
|
||||
if (parameters.Length > 0)
|
||||
command.Parameters.AddRange(parameters);
|
||||
|
||||
var result = command.ExecuteScalar();
|
||||
return Task.FromResult(result);
|
||||
return command.ExecuteScalar();
|
||||
}
|
||||
|
||||
public void Commit()
|
||||
@@ -77,8 +82,18 @@ internal class AccessTransaction : IAccessTransaction
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
_transaction.Dispose();
|
||||
_connection.Dispose();
|
||||
try
|
||||
{
|
||||
_transaction.Dispose();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_ownsConnection)
|
||||
{
|
||||
_connection.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
@@ -144,68 +159,82 @@ internal class AccessTransaction : IAccessTransaction
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Access 数据服务实现
|
||||
/// Access 数据服务实现(基于 System.Data.OleDb)
|
||||
/// </summary>
|
||||
public class AccessDataService : IAccessDataService
|
||||
{
|
||||
private readonly AccessDataServiceOptions _options;
|
||||
private readonly AccessDatabaseOptions _options;
|
||||
private readonly IAccessConnectionStringFactory _connectionFactory;
|
||||
private readonly IAccessDatabaseInitializer _initializer;
|
||||
private readonly IAccessPasswordDecryptor _decryptor;
|
||||
private readonly ILogger<AccessDataService>? _logger;
|
||||
private readonly string _connectionString;
|
||||
|
||||
public AccessDataService(AccessDataServiceOptions options, ILogger<AccessDataService>? logger = null)
|
||||
public AccessDataService(
|
||||
AccessDatabaseOptions options,
|
||||
IAccessConnectionStringFactory? connectionFactory = null,
|
||||
IAccessDatabaseInitializer? initializer = null,
|
||||
IAccessPasswordDecryptor? decryptor = null,
|
||||
ILogger<AccessDataService>? logger = null)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_options.Validate();
|
||||
|
||||
_connectionFactory = connectionFactory ?? new AccessConnectionStringFactory(decryptor ?? new PassthroughAccessPasswordDecryptor());
|
||||
_initializer = initializer ?? new AccessDatabaseInitializer();
|
||||
_decryptor = decryptor ?? new PassthroughAccessPasswordDecryptor();
|
||||
_logger = logger;
|
||||
|
||||
if (string.IsNullOrEmpty(_options.DatabasePath))
|
||||
try
|
||||
{
|
||||
throw new ArgumentException("数据库路径不能为空", nameof(options.DatabasePath));
|
||||
}
|
||||
// 确保数据库存在(如果配置了模板复制)
|
||||
_initializer.EnsureDatabase(_options, NullServiceProvider.Instance);
|
||||
|
||||
if (!File.Exists(_options.DatabasePath))
|
||||
// 构建连接字符串
|
||||
_connectionString = _connectionFactory.Create(_options, NullServiceProvider.Instance);
|
||||
|
||||
_logger?.LogInformation("AccessDataService 已初始化,数据库: {DatabaseFile}, Provider: {Provider}",
|
||||
_options.DatabaseFile, _options.ProviderName);
|
||||
_logger?.LogDebug("实际连接字符串: {ConnectionString}", _connectionString);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new FileNotFoundException($"找不到 Access 数据库文件: {_options.DatabasePath}");
|
||||
_logger?.LogError(ex, "AccessDataService 初始化失败");
|
||||
|
||||
// 提供详细的错误诊断信息
|
||||
if (ex.Message.Contains("找不到") || ex is FileNotFoundException)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"找不到 Access 数据库文件: {_options.DatabaseFile}。" +
|
||||
$"请确保文件存在或配置 TemplateFile 以自动创建。", ex);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<T>> QueryAsync<T>(string sql, params OleDbParameter[] parameters) where T : new()
|
||||
public List<T> Query<T>(string sql, params OleDbParameter[] parameters) where T : new()
|
||||
{
|
||||
_logger?.LogDebug("执行查询: {Sql}", sql);
|
||||
|
||||
return await Task.Run(() =>
|
||||
return ExecuteWithRetry(() =>
|
||||
{
|
||||
using var connection = CreateConnection();
|
||||
connection.Open();
|
||||
|
||||
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();
|
||||
|
||||
LogFieldMap(reader);
|
||||
|
||||
if (_logger?.IsEnabled(LogLevel.Information) == true)
|
||||
{
|
||||
var columns = Enumerable.Range(0, reader.FieldCount)
|
||||
.Select(i => $"{reader.GetName(i)}(idx:{i})")
|
||||
.ToArray();
|
||||
_logger.LogInformation("查询列: {Columns}", string.Join(", ", columns));
|
||||
}
|
||||
|
||||
var rowIndex = 0;
|
||||
while (reader.Read())
|
||||
{
|
||||
rowIndex++;
|
||||
|
||||
if (_logger?.IsEnabled(LogLevel.Information) == true)
|
||||
{
|
||||
var fields = Enumerable.Range(0, reader.FieldCount)
|
||||
.Select(i => $"{reader.GetName(i)}='{SafeGetValue(reader, i)}'")
|
||||
.ToArray();
|
||||
_logger.LogInformation("Row {RowIndex}: {Fields}", rowIndex, string.Join(", ", fields));
|
||||
}
|
||||
|
||||
var item = MapReaderToObject<T>(reader);
|
||||
results.Add(item);
|
||||
}
|
||||
@@ -215,29 +244,25 @@ public class AccessDataService : IAccessDataService
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<T?> QueryFirstOrDefaultAsync<T>(string sql, params OleDbParameter[] parameters) where T : new()
|
||||
public T? QueryFirstOrDefault<T>(string sql, params OleDbParameter[] parameters) where T : new()
|
||||
{
|
||||
_logger?.LogDebug("执行单条查询: {Sql}", sql);
|
||||
|
||||
return await Task.Run(() =>
|
||||
return ExecuteWithRetry(() =>
|
||||
{
|
||||
using var connection = CreateConnection();
|
||||
connection.Open();
|
||||
|
||||
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();
|
||||
|
||||
if (_logger?.IsEnabled(LogLevel.Debug) == true)
|
||||
{
|
||||
var columns = Enumerable.Range(0, reader.FieldCount)
|
||||
.Select(reader.GetName)
|
||||
.ToArray();
|
||||
_logger.LogDebug("查询列: {Columns}", string.Join(", ", columns));
|
||||
}
|
||||
|
||||
if (reader.Read())
|
||||
{
|
||||
return MapReaderToObject<T>(reader);
|
||||
@@ -247,18 +272,22 @@ public class AccessDataService : IAccessDataService
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<object?> ExecuteScalarAsync(string sql, params OleDbParameter[] parameters)
|
||||
public object? ExecuteScalar(string sql, params OleDbParameter[] parameters)
|
||||
{
|
||||
_logger?.LogDebug("执行标量查询: {Sql}", sql);
|
||||
|
||||
return await Task.Run(() =>
|
||||
return ExecuteWithRetry(() =>
|
||||
{
|
||||
using var connection = CreateConnection();
|
||||
connection.Open();
|
||||
|
||||
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);
|
||||
@@ -266,18 +295,22 @@ public class AccessDataService : IAccessDataService
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<int> ExecuteAsync(string sql, params OleDbParameter[] parameters)
|
||||
public int Execute(string sql, params OleDbParameter[] parameters)
|
||||
{
|
||||
_logger?.LogDebug("执行命令: {Sql}", sql);
|
||||
|
||||
return await Task.Run(() =>
|
||||
return ExecuteWithRetry(() =>
|
||||
{
|
||||
using var connection = CreateConnection();
|
||||
connection.Open();
|
||||
|
||||
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);
|
||||
@@ -290,17 +323,17 @@ public class AccessDataService : IAccessDataService
|
||||
_logger?.LogDebug("开始事务");
|
||||
var connection = CreateConnection();
|
||||
connection.Open();
|
||||
return new AccessTransaction(connection, _logger);
|
||||
return new AccessTransaction(connection, ownsConnection: true, _logger);
|
||||
}
|
||||
|
||||
public async Task ExecuteInTransactionAsync(Func<IAccessTransaction, Task> action)
|
||||
public void ExecuteInTransaction(Action<IAccessTransaction> action)
|
||||
{
|
||||
_logger?.LogDebug("在事务中执行操作");
|
||||
|
||||
using var transaction = BeginTransaction();
|
||||
try
|
||||
{
|
||||
await action(transaction);
|
||||
action(transaction);
|
||||
transaction.Commit();
|
||||
_logger?.LogDebug("事务执行成功");
|
||||
}
|
||||
@@ -312,13 +345,13 @@ public class AccessDataService : IAccessDataService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> TestConnectionAsync()
|
||||
public bool TestConnection()
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger?.LogDebug("测试数据库连接");
|
||||
using var connection = CreateConnection();
|
||||
await connection.OpenAsync();
|
||||
connection.Open();
|
||||
_logger?.LogDebug("数据库连接成功");
|
||||
return true;
|
||||
}
|
||||
@@ -331,30 +364,81 @@ public class AccessDataService : IAccessDataService
|
||||
|
||||
private OleDbConnection CreateConnection()
|
||||
{
|
||||
return new OleDbConnection(_options.ConnectionString);
|
||||
return new OleDbConnection(_connectionString);
|
||||
}
|
||||
|
||||
private void LogFieldMap(IDataReader reader)
|
||||
private T ExecuteWithRetry<T>(Func<T> operation)
|
||||
{
|
||||
if (!(_logger?.IsEnabled(LogLevel.Information) ?? false)) return;
|
||||
var maxRetries = _options.MaxRetryCount;
|
||||
var retryDelay = _options.RetryDelayMilliseconds;
|
||||
|
||||
var columns = Enumerable.Range(0, reader.FieldCount)
|
||||
.Select(i => $"'{reader.GetName(i)}'->idx:{i}")
|
||||
.ToArray();
|
||||
_logger!.LogInformation("字段映射: {Columns}", string.Join(", ", columns));
|
||||
for (int attempt = 0; attempt <= maxRetries; attempt++)
|
||||
{
|
||||
try
|
||||
{
|
||||
return operation();
|
||||
}
|
||||
catch (OleDbException ex) when (attempt < maxRetries && IsRetriableError(ex))
|
||||
{
|
||||
_logger?.LogWarning(ex, "数据库操作失败,正在重试 ({Attempt}/{MaxRetries})...", attempt + 1, maxRetries);
|
||||
Thread.Sleep(retryDelay);
|
||||
}
|
||||
catch (OleDbException ex)
|
||||
{
|
||||
// 提供详细的错误诊断信息
|
||||
var errorMessage = $"Access 数据库操作失败: {ex.Message}";
|
||||
|
||||
// 检查是否是 Provider 相关的错误
|
||||
if (ex.Message.Contains("找不到") || ex.Message.Contains("未注册") ||
|
||||
ex.Message.Contains("OLE DB") || ex.Message.Contains("多步"))
|
||||
{
|
||||
errorMessage += $"\n\n可能的原因:" +
|
||||
$"\n1. 未安装 Access Database Engine:当前使用 Provider = {_options.ProviderName}" +
|
||||
$"\n - 如果使用 16.0,需要安装 Access Database Engine 2016 或 Office 2016+" +
|
||||
$"\n - 如果使用 12.0,需要安装 Access Database Engine 2010 或 Office 2010+" +
|
||||
$"\n - 建议:优先使用 12.0 版本,兼容性最好" +
|
||||
$"\n2. 32位/64位不匹配:进程架构必须与 Access Driver 架构一致" +
|
||||
$"\n3. 数据库文件损坏或被其他程序锁定" +
|
||||
$"\n4. 数据库文件路径不正确:{_options.DatabaseFile}";
|
||||
|
||||
_logger?.LogError(ex, "数据库连接失败,Provider: {Provider}", _options.ProviderName);
|
||||
}
|
||||
|
||||
throw new InvalidOperationException(errorMessage, ex);
|
||||
}
|
||||
}
|
||||
|
||||
// 不会到这里,因为最后一次尝试会抛出异常
|
||||
throw new InvalidOperationException("重试逻辑错误");
|
||||
}
|
||||
|
||||
private static object? SafeGetValue(IDataRecord reader, int ordinal)
|
||||
private static bool IsRetriableError(OleDbException ex)
|
||||
{
|
||||
try
|
||||
// Access 数据库常见的可重试错误
|
||||
// -2147467259: 数据库被锁定
|
||||
// -2147217865: 无法打开数据库(可能正在被其他进程使用)
|
||||
return ex.ErrorCode == -2147467259 || ex.ErrorCode == -2147217865;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 克隆 OleDbParameter 以避免重试时参数被重复添加的问题
|
||||
/// </summary>
|
||||
private static OleDbParameter CloneParameter(OleDbParameter original)
|
||||
{
|
||||
var clone = new OleDbParameter
|
||||
{
|
||||
return reader.GetValue(ordinal);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 某些 OleDb 列类型在获取值时可能抛 COM 异常,这里做容错日志使用
|
||||
return "<unavailable>";
|
||||
}
|
||||
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()
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Sharp.Data.Access.Options;
|
||||
using Sharp.Data.Access.Security;
|
||||
using Sharp.Data.Access.Connections;
|
||||
using Sharp.Data.Access.Initialization;
|
||||
|
||||
namespace Sharp.Data.Access;
|
||||
|
||||
@@ -8,25 +13,63 @@ namespace Sharp.Data.Access;
|
||||
public static class AccessDataServiceExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 添加 Access 数据服务
|
||||
/// 添加 Access 数据服务(最简单方式 - 仅指定数据库路径)
|
||||
/// </summary>
|
||||
/// <param name="services">服务集合</param>
|
||||
/// <param name="databasePath">数据库文件路径</param>
|
||||
/// <param name="provider">OLE DB 提供程序(默认:Microsoft.ACE.OLEDB.12.0)</param>
|
||||
/// <param name="databasePath">数据库文件路径(绝对或相对路径)</param>
|
||||
/// <param name="password">数据库密码(可选)</param>
|
||||
/// <param name="provider">OLE DB 提供程序,默认:Microsoft.ACE.OLEDB.16.0</param>
|
||||
/// <returns>服务集合</returns>
|
||||
public static IServiceCollection AddAccessDataService(
|
||||
this IServiceCollection services,
|
||||
string databasePath,
|
||||
string provider = "Microsoft.ACE.OLEDB.12.0")
|
||||
string? password = null,
|
||||
string? provider = null)
|
||||
{
|
||||
var options = new AccessDataServiceOptions
|
||||
return services.AddAccessDataService(options =>
|
||||
{
|
||||
DatabasePath = databasePath,
|
||||
Provider = provider
|
||||
};
|
||||
options.DatabaseFile = databasePath;
|
||||
if (!string.IsNullOrEmpty(password))
|
||||
options.Password = password;
|
||||
if (!string.IsNullOrEmpty(provider))
|
||||
options.ProviderName = provider;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加 Access 数据服务(使用配置委托)
|
||||
/// </summary>
|
||||
/// <param name="services">服务集合</param>
|
||||
/// <param name="configureOptions">配置委托</param>
|
||||
/// <returns>服务集合</returns>
|
||||
public static IServiceCollection AddAccessDataService(
|
||||
this IServiceCollection services,
|
||||
Action<AccessDatabaseOptions> configureOptions)
|
||||
{
|
||||
if (services == null) throw new ArgumentNullException(nameof(services));
|
||||
if (configureOptions == null) throw new ArgumentNullException(nameof(configureOptions));
|
||||
|
||||
var options = new AccessDatabaseOptions();
|
||||
configureOptions(options);
|
||||
options.Validate();
|
||||
|
||||
// 注册核心服务
|
||||
services.TryAddSingleton<IAccessPasswordDecryptor, PassthroughAccessPasswordDecryptor>();
|
||||
services.TryAddSingleton<IAccessConnectionStringFactory>(sp =>
|
||||
new AccessConnectionStringFactory(sp.GetRequiredService<IAccessPasswordDecryptor>()));
|
||||
services.TryAddSingleton<IAccessDatabaseInitializer, AccessDatabaseInitializer>();
|
||||
|
||||
// 注册配置
|
||||
services.AddSingleton(options);
|
||||
services.AddSingleton<IAccessDataService, AccessDataService>();
|
||||
|
||||
// 注册 AccessDataService
|
||||
services.AddSingleton<IAccessDataService>(sp =>
|
||||
new AccessDataService(
|
||||
options,
|
||||
sp.GetRequiredService<IAccessConnectionStringFactory>(),
|
||||
sp.GetRequiredService<IAccessDatabaseInitializer>(),
|
||||
sp.GetRequiredService<IAccessPasswordDecryptor>(),
|
||||
sp.GetService<Microsoft.Extensions.Logging.ILogger<AccessDataService>>()));
|
||||
|
||||
return services;
|
||||
}
|
||||
@@ -39,30 +82,37 @@ public static class AccessDataServiceExtensions
|
||||
/// <returns>服务集合</returns>
|
||||
public static IServiceCollection AddAccessDataService(
|
||||
this IServiceCollection services,
|
||||
AccessDataServiceOptions options)
|
||||
AccessDatabaseOptions options)
|
||||
{
|
||||
services.AddSingleton(options);
|
||||
services.AddSingleton<IAccessDataService, AccessDataService>();
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
|
||||
return services.AddAccessDataService(_ => { });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册自定义密码解密器
|
||||
/// </summary>
|
||||
/// <typeparam name="TDecryptor">解密器类型</typeparam>
|
||||
/// <param name="services">服务集合</param>
|
||||
/// <returns>服务集合</returns>
|
||||
public static IServiceCollection AddAccessPasswordDecryptor<TDecryptor>(this IServiceCollection services)
|
||||
where TDecryptor : class, IAccessPasswordDecryptor
|
||||
{
|
||||
services.AddSingleton<IAccessPasswordDecryptor, TDecryptor>();
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加 Access 数据服务(使用配置委托)
|
||||
/// 注册自定义密码解密器(使用工厂方法)
|
||||
/// </summary>
|
||||
/// <param name="services">服务集合</param>
|
||||
/// <param name="configureOptions">配置委托</param>
|
||||
/// <param name="implementationFactory">解密器工厂</param>
|
||||
/// <returns>服务集合</returns>
|
||||
public static IServiceCollection AddAccessDataService(
|
||||
public static IServiceCollection AddAccessPasswordDecryptor(
|
||||
this IServiceCollection services,
|
||||
Action<AccessDataServiceOptions> configureOptions)
|
||||
Func<IServiceProvider, IAccessPasswordDecryptor> implementationFactory)
|
||||
{
|
||||
var options = new AccessDataServiceOptions();
|
||||
configureOptions(options);
|
||||
|
||||
services.AddSingleton(options);
|
||||
services.AddSingleton<IAccessDataService, AccessDataService>();
|
||||
|
||||
services.AddSingleton(implementationFactory);
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
namespace Sharp.Data.Access;
|
||||
|
||||
/// <summary>
|
||||
/// Access 数据服务配置选项
|
||||
/// </summary>
|
||||
public class AccessDataServiceOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据库文件路径
|
||||
/// </summary>
|
||||
public string DatabasePath { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// OLE DB 提供程序(默认:Microsoft.ACE.OLEDB.12.0)
|
||||
/// </summary>
|
||||
public string Provider { get; set; } = "Microsoft.ACE.OLEDB.12.0";
|
||||
|
||||
/// <summary>
|
||||
/// 完整的连接字符串
|
||||
/// </summary>
|
||||
public string ConnectionString => $"Provider={Provider};Data Source={DatabasePath}";
|
||||
}
|
||||
@@ -29,6 +29,8 @@ internal sealed class AccessConnectionStringFactory : IAccessConnectionStringFac
|
||||
throw new InvalidOperationException("Access 数据库需要提供 DatabaseFile 或 ConnectionString。");
|
||||
}
|
||||
|
||||
// 基础连接字符串
|
||||
// 注意:Connect Timeout 参数在某些 ACE 版本中不稳定,已移除
|
||||
var builder = new OleDbConnectionStringBuilder
|
||||
{
|
||||
["Provider"] = configuration.ProviderName,
|
||||
|
||||
@@ -10,17 +10,17 @@ public interface IAccessTransaction : IDisposable
|
||||
/// <summary>
|
||||
/// 执行非查询命令(INSERT, UPDATE, DELETE)
|
||||
/// </summary>
|
||||
Task<int> ExecuteAsync(string sql, params OleDbParameter[] parameters);
|
||||
int Execute(string sql, params OleDbParameter[] parameters);
|
||||
|
||||
/// <summary>
|
||||
/// 查询多条记录
|
||||
/// </summary>
|
||||
Task<List<T>> QueryAsync<T>(string sql, params OleDbParameter[] parameters) where T : new();
|
||||
List<T> Query<T>(string sql, params OleDbParameter[] parameters) where T : new();
|
||||
|
||||
/// <summary>
|
||||
/// 执行标量查询(COUNT, SUM等)
|
||||
/// </summary>
|
||||
Task<object?> ExecuteScalarAsync(string sql, params OleDbParameter[] parameters);
|
||||
object? ExecuteScalar(string sql, params OleDbParameter[] parameters);
|
||||
|
||||
/// <summary>
|
||||
/// 提交事务
|
||||
@@ -34,29 +34,29 @@ public interface IAccessTransaction : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Access 数据服务接口
|
||||
/// Access 数据服务接口(基于 System.Data.OleDb)
|
||||
/// </summary>
|
||||
public interface IAccessDataService
|
||||
{
|
||||
/// <summary>
|
||||
/// 查询多条记录
|
||||
/// </summary>
|
||||
Task<List<T>> QueryAsync<T>(string sql, params OleDbParameter[] parameters) where T : new();
|
||||
List<T> Query<T>(string sql, params OleDbParameter[] parameters) where T : new();
|
||||
|
||||
/// <summary>
|
||||
/// 查询单条记录
|
||||
/// </summary>
|
||||
Task<T?> QueryFirstOrDefaultAsync<T>(string sql, params OleDbParameter[] parameters) where T : new();
|
||||
T? QueryFirstOrDefault<T>(string sql, params OleDbParameter[] parameters) where T : new();
|
||||
|
||||
/// <summary>
|
||||
/// 执行标量查询(COUNT, SUM等)
|
||||
/// </summary>
|
||||
Task<object?> ExecuteScalarAsync(string sql, params OleDbParameter[] parameters);
|
||||
object? ExecuteScalar(string sql, params OleDbParameter[] parameters);
|
||||
|
||||
/// <summary>
|
||||
/// 执行非查询命令(INSERT, UPDATE, DELETE)
|
||||
/// </summary>
|
||||
Task<int> ExecuteAsync(string sql, params OleDbParameter[] parameters);
|
||||
int Execute(string sql, params OleDbParameter[] parameters);
|
||||
|
||||
/// <summary>
|
||||
/// 开始事务
|
||||
@@ -66,10 +66,10 @@ public interface IAccessDataService
|
||||
/// <summary>
|
||||
/// 在事务中执行操作(自动提交/回滚)
|
||||
/// </summary>
|
||||
Task ExecuteInTransactionAsync(Func<IAccessTransaction, Task> action);
|
||||
void ExecuteInTransaction(Action<IAccessTransaction> action);
|
||||
|
||||
/// <summary>
|
||||
/// 测试数据库连接
|
||||
/// </summary>
|
||||
Task<bool> TestConnectionAsync();
|
||||
bool TestConnection();
|
||||
}
|
||||
@@ -12,6 +12,9 @@ internal sealed record AccessDatabaseConfiguration(
|
||||
string? EncryptedPassword,
|
||||
string? TemplateFile,
|
||||
string? DataDirectory,
|
||||
int ConnectionTimeout,
|
||||
int MaxRetryCount,
|
||||
int RetryDelayMilliseconds,
|
||||
IReadOnlyDictionary<string, string> AdditionalParameters,
|
||||
bool CopyTemplateWhenMissing,
|
||||
bool ThrowIfMissing)
|
||||
@@ -38,6 +41,9 @@ internal sealed record AccessDatabaseConfiguration(
|
||||
encryptedPassword,
|
||||
templateFile,
|
||||
dataDirectory,
|
||||
options.ConnectionTimeout,
|
||||
options.MaxRetryCount,
|
||||
options.RetryDelayMilliseconds,
|
||||
new Dictionary<string, string>(options.AdditionalParameters, StringComparer.OrdinalIgnoreCase),
|
||||
options.CopyTemplateWhenMissing,
|
||||
options.ThrowIfMissing);
|
||||
|
||||
@@ -4,7 +4,7 @@ using System.Collections.Generic;
|
||||
namespace Sharp.Data.Access.Options;
|
||||
|
||||
/// <summary>
|
||||
/// Access 数据库配置项,调用方负责提供所有需要的参数。
|
||||
/// Access 数据库统一配置项,同时支持 OleDb 和 EF Core 两种方式。
|
||||
/// </summary>
|
||||
public class AccessDatabaseOptions
|
||||
{
|
||||
@@ -40,7 +40,7 @@ public class AccessDatabaseOptions
|
||||
public Func<IServiceProvider, string?>? DataDirectoryFactory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 明文密码,可选。
|
||||
/// 明文密码,可选。用于访问加密的 Access 数据库。
|
||||
/// </summary>
|
||||
public string? Password { get; set; }
|
||||
|
||||
@@ -77,13 +77,32 @@ public class AccessDatabaseOptions
|
||||
/// <summary>
|
||||
/// 当数据库文件缺失(且无法复制)时是否抛出异常。
|
||||
/// </summary>
|
||||
public bool ThrowIfMissing { get; set; }
|
||||
public bool ThrowIfMissing { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Provider 名称,默认为 ACE OLE DB 16。
|
||||
/// Provider 名称,默认为 ACE OLE DB 16.0(功能最完整)。
|
||||
/// 可选值:
|
||||
/// - AccessProviderNames.AceOleDb16 (Microsoft.ACE.OLEDB.16.0) - 推荐,需要 Office 2016+
|
||||
/// - AccessProviderNames.AceOleDb12 (Microsoft.ACE.OLEDB.12.0) - 需要 Office 2010+,不支持 Connect Timeout
|
||||
/// - AccessProviderNames.JetOleDb4 (Microsoft.Jet.OLEDB.4.0) - 仅支持 32 位和旧版 Access
|
||||
/// </summary>
|
||||
public string ProviderName { get; set; } = AccessProviderNames.AceOleDb16;
|
||||
|
||||
/// <summary>
|
||||
/// 连接超时时间(秒),默认 15 秒。
|
||||
/// </summary>
|
||||
public int ConnectionTimeout { get; set; } = 15;
|
||||
|
||||
/// <summary>
|
||||
/// 最大重试次数,默认 3 次。用于增强稳健性。
|
||||
/// </summary>
|
||||
public int MaxRetryCount { get; set; } = 3;
|
||||
|
||||
/// <summary>
|
||||
/// 重试延迟(毫秒),默认 100 毫秒。
|
||||
/// </summary>
|
||||
public int RetryDelayMilliseconds { get; set; } = 100;
|
||||
|
||||
/// <summary>
|
||||
/// 额外的 OLE DB 参数,按需添加。
|
||||
/// </summary>
|
||||
@@ -105,5 +124,20 @@ public class AccessDatabaseOptions
|
||||
{
|
||||
throw new InvalidOperationException("必须指定 Access Provider。");
|
||||
}
|
||||
|
||||
if (ConnectionTimeout <= 0)
|
||||
{
|
||||
throw new InvalidOperationException("ConnectionTimeout 必须大于 0。");
|
||||
}
|
||||
|
||||
if (MaxRetryCount < 0)
|
||||
{
|
||||
throw new InvalidOperationException("MaxRetryCount 不能小于 0。");
|
||||
}
|
||||
|
||||
if (RetryDelayMilliseconds < 0)
|
||||
{
|
||||
throw new InvalidOperationException("RetryDelayMilliseconds 不能小于 0。");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)!;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using Sharp.Log;
|
||||
using Sharp.Data;
|
||||
using Sharp.Data.Context;
|
||||
using Sharp.Data.Access;
|
||||
using Sharp.Data.Access.Options;
|
||||
using Server.Config;
|
||||
using Server.Services;
|
||||
using Server.Data;
|
||||
@@ -110,7 +111,7 @@ namespace Server
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void InitializeAccessDataService()
|
||||
{
|
||||
if (LoggerFactory == null)
|
||||
@@ -123,13 +124,39 @@ namespace Server
|
||||
|
||||
try
|
||||
{
|
||||
var databasePath = Path.GetFullPath("dbDwDmsSetup.accdb");
|
||||
var options = new AccessDataServiceOptions
|
||||
// 诊断:输出当前目录信息
|
||||
logger.LogInformation("AppContext.BaseDirectory: {BaseDirectory}", AppContext.BaseDirectory);
|
||||
logger.LogInformation("Environment.CurrentDirectory: {CurrentDirectory}", Environment.CurrentDirectory);
|
||||
logger.LogInformation("数据库文件在输出目录: {DbPath}",
|
||||
Path.Combine(AppContext.BaseDirectory, "dbDwDmsSetup.accdb"));
|
||||
logger.LogInformation("数据库文件是否存在: {Exists}",
|
||||
File.Exists(Path.Combine(AppContext.BaseDirectory, "dbDwDmsSetup.accdb")));
|
||||
|
||||
// 配置 Access 数据库选项
|
||||
var options = new AccessDatabaseOptions
|
||||
{
|
||||
DatabasePath = databasePath
|
||||
DatabaseFile = "dbDwDmsSetup.accdb",
|
||||
|
||||
// 使用 ACE OLE DB 16.0(功能最完整,支持所有连接字符串参数)
|
||||
// 注意:12.0 不支持 Connect Timeout 参数,会导致 0x80040E21 错误
|
||||
ProviderName = AccessProviderNames.AceOleDb16,
|
||||
|
||||
// 如果数据库有密码,可以在这里设置:
|
||||
// Password = "your_password",
|
||||
|
||||
// 或者使用加密密码(需要先注册自定义解密器):
|
||||
// EncryptedPassword = "encrypted_password",
|
||||
|
||||
// 可选:调整稳健性配置
|
||||
// MaxRetryCount = 3, // 最大重试次数
|
||||
// RetryDelayMilliseconds = 100, // 重试延迟
|
||||
// ConnectionTimeout = 15, // 连接超时(秒)
|
||||
};
|
||||
|
||||
AccessDataService = new AccessDataService(options, LoggerFactory.CreateLogger<AccessDataService>());
|
||||
// 创建 Access 数据服务
|
||||
AccessDataService = new AccessDataService(
|
||||
options,
|
||||
logger: LoggerFactory.CreateLogger<AccessDataService>());
|
||||
|
||||
logger.LogInformation("Access 数据服务初始化成功");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UseWPF>true</UseWPF>
|
||||
|
||||
<!-- 强制使用 x64 架构,与已安装的 64 位 ACE 驱动匹配 -->
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
<SelfContained>true</SelfContained>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using System;
|
||||
using System.Data.OleDb;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using EntityFrameworkCore.Jet;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Sharp.Data.Access.Options;
|
||||
using Server.Data.Access;
|
||||
|
||||
namespace Server.Views;
|
||||
@@ -110,7 +112,7 @@ public partial class AccessDatabaseWindow : Window
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建 Access 数据库上下文
|
||||
/// 创建 Access 数据库上下文(使用新的统一配置体系)
|
||||
/// </summary>
|
||||
private AccessDbContext CreateDbContext()
|
||||
{
|
||||
@@ -122,8 +124,18 @@ public partial class AccessDatabaseWindow : Window
|
||||
throw new FileNotFoundException($"找不到 Access 数据库文件: {dbPath}");
|
||||
}
|
||||
|
||||
// 构建连接字符串
|
||||
var connectionString = $"Provider=Microsoft.ACE.OLEDB.12.0;Data Source={dbPath}";
|
||||
// 使用统一的配置方式构建连接字符串
|
||||
var builder = new OleDbConnectionStringBuilder
|
||||
{
|
||||
["Provider"] = AccessProviderNames.AceOleDb16, // 使用 16.0
|
||||
["Data Source"] = dbPath
|
||||
// Connect Timeout 在某些情况下不稳定,已移除
|
||||
};
|
||||
|
||||
// 如果数据库有密码,可以在这里设置:
|
||||
// builder["Jet OLEDB:Database Password"] = "your_password";
|
||||
|
||||
var connectionString = builder.ConnectionString;
|
||||
|
||||
// 创建 DbContext 选项
|
||||
var optionsBuilder = new DbContextOptionsBuilder<AccessDbContext>();
|
||||
|
||||
@@ -46,14 +46,14 @@ public partial class AccessDatabaseWindowOleDb : Window
|
||||
|
||||
private async void Window_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await LoadDataAsync();
|
||||
await Task.Run(() => LoadData());
|
||||
}
|
||||
|
||||
#region 基础操作
|
||||
|
||||
private async void RefreshButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await LoadDataAsync();
|
||||
await Task.Run(() => LoadData());
|
||||
}
|
||||
|
||||
private async void TestConnectionButton_Click(object sender, RoutedEventArgs e)
|
||||
@@ -68,7 +68,7 @@ public partial class AccessDatabaseWindowOleDb : Window
|
||||
try
|
||||
{
|
||||
_logger?.LogInformation("测试 Access 数据库连接...");
|
||||
var success = await _accessDataService.TestConnectionAsync();
|
||||
var success = await Task.Run(() => _accessDataService.TestConnection());
|
||||
|
||||
if (success)
|
||||
{
|
||||
@@ -120,31 +120,42 @@ public partial class AccessDatabaseWindowOleDb : Window
|
||||
|
||||
try
|
||||
{
|
||||
// 检查用户是否已存在
|
||||
var count = await _accessDataService.ExecuteScalarAsync(
|
||||
"SELECT COUNT(*) FROM UserInfo WHERE UserName = ?",
|
||||
new OleDbParameter("@UserName", userName));
|
||||
|
||||
if (Convert.ToInt32(count) > 0)
|
||||
await Task.Run(() =>
|
||||
{
|
||||
MessageBox.Show($"用户 '{userName}' 已存在!", "警告",
|
||||
MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
// 检查用户是否已存在
|
||||
var count = _accessDataService.ExecuteScalar(
|
||||
"SELECT COUNT(*) FROM UserInfo WHERE UserName = ?",
|
||||
new OleDbParameter("@UserName", userName));
|
||||
|
||||
// 插入新用户
|
||||
await _accessDataService.ExecuteAsync(
|
||||
"INSERT INTO UserInfo (UserName, [Password]) VALUES (?, ?)",
|
||||
new OleDbParameter("@UserName", userName),
|
||||
new OleDbParameter("@Password", password));
|
||||
if (Convert.ToInt32(count) > 0)
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
MessageBox.Show($"用户 '{userName}' 已存在!", "警告",
|
||||
MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
_logger?.LogInformation($"成功添加用户: {userName}");
|
||||
MessageBox.Show($"成功添加用户 '{userName}'!", "成功",
|
||||
MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
// 插入新用户
|
||||
_accessDataService.Execute(
|
||||
"INSERT INTO UserInfo (UserName, [Password]) VALUES (?, ?)",
|
||||
new OleDbParameter("@UserName", userName),
|
||||
new OleDbParameter("@Password", password));
|
||||
|
||||
UserNameTextBox.Clear();
|
||||
PasswordTextBox.Clear();
|
||||
await LoadDataAsync();
|
||||
_logger?.LogInformation($"成功添加用户: {userName}");
|
||||
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
MessageBox.Show($"成功添加用户 '{userName}'!", "成功",
|
||||
MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
|
||||
UserNameTextBox.Clear();
|
||||
PasswordTextBox.Clear();
|
||||
});
|
||||
|
||||
LoadData();
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -189,25 +200,36 @@ public partial class AccessDatabaseWindowOleDb : Window
|
||||
|
||||
try
|
||||
{
|
||||
// 更新密码
|
||||
var rowsAffected = await _accessDataService.ExecuteAsync(
|
||||
"UPDATE UserInfo SET [Password] = ? WHERE UserName = ?",
|
||||
new OleDbParameter("@Password", newPassword),
|
||||
new OleDbParameter("@UserName", userName));
|
||||
|
||||
if (rowsAffected == 0)
|
||||
await Task.Run(() =>
|
||||
{
|
||||
MessageBox.Show($"用户 '{userName}' 不存在!", "警告",
|
||||
MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
// 更新密码
|
||||
var rowsAffected = _accessDataService.Execute(
|
||||
"UPDATE UserInfo SET [Password] = ? WHERE UserName = ?",
|
||||
new OleDbParameter("@Password", newPassword),
|
||||
new OleDbParameter("@UserName", userName));
|
||||
|
||||
_logger?.LogInformation($"成功修改用户 {userName} 的密码");
|
||||
MessageBox.Show($"成功修改用户 '{userName}' 的密码!", "成功",
|
||||
MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
if (rowsAffected == 0)
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
MessageBox.Show($"用户 '{userName}' 不存在!", "警告",
|
||||
MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
PasswordTextBox.Clear();
|
||||
await LoadDataAsync();
|
||||
_logger?.LogInformation($"成功修改用户 {userName} 的密码");
|
||||
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
MessageBox.Show($"成功修改用户 '{userName}' 的密码!", "成功",
|
||||
MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
|
||||
PasswordTextBox.Clear();
|
||||
});
|
||||
|
||||
LoadData();
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -245,31 +267,39 @@ public partial class AccessDatabaseWindowOleDb : Window
|
||||
try
|
||||
{
|
||||
// 使用事务删除用户及其权限
|
||||
await _accessDataService.ExecuteInTransactionAsync(async transaction =>
|
||||
await Task.Run(() =>
|
||||
{
|
||||
// 先删除用户的所有权限
|
||||
await transaction.ExecuteAsync(
|
||||
"DELETE FROM UserPermission WHERE UserInfo_UserName = ?",
|
||||
new OleDbParameter("@UserName", userName));
|
||||
|
||||
// 再删除用户
|
||||
var rowsAffected = await transaction.ExecuteAsync(
|
||||
"DELETE FROM UserInfo WHERE UserName = ?",
|
||||
new OleDbParameter("@UserName", userName));
|
||||
|
||||
if (rowsAffected == 0)
|
||||
_accessDataService.ExecuteInTransaction(transaction =>
|
||||
{
|
||||
throw new InvalidOperationException($"用户 '{userName}' 不存在!");
|
||||
}
|
||||
// 先删除用户的所有权限
|
||||
transaction.Execute(
|
||||
"DELETE FROM UserPermission WHERE UserInfo_UserName = ?",
|
||||
new OleDbParameter("@UserName", userName));
|
||||
|
||||
// 再删除用户
|
||||
var rowsAffected = transaction.Execute(
|
||||
"DELETE FROM UserInfo WHERE UserName = ?",
|
||||
new OleDbParameter("@UserName", userName));
|
||||
|
||||
if (rowsAffected == 0)
|
||||
{
|
||||
throw new InvalidOperationException($"用户 '{userName}' 不存在!");
|
||||
}
|
||||
});
|
||||
|
||||
_logger?.LogInformation($"成功删除用户: {userName}");
|
||||
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
MessageBox.Show($"成功删除用户 '{userName}'!", "成功",
|
||||
MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
|
||||
UserNameTextBox.Clear();
|
||||
PasswordTextBox.Clear();
|
||||
});
|
||||
|
||||
LoadData();
|
||||
});
|
||||
|
||||
_logger?.LogInformation($"成功删除用户: {userName}");
|
||||
MessageBox.Show($"成功删除用户 '{userName}'!", "成功",
|
||||
MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
|
||||
UserNameTextBox.Clear();
|
||||
PasswordTextBox.Clear();
|
||||
await LoadDataAsync();
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
@@ -312,31 +342,41 @@ public partial class AccessDatabaseWindowOleDb : Window
|
||||
|
||||
try
|
||||
{
|
||||
// 检查权限是否已存在
|
||||
var count = await _accessDataService.ExecuteScalarAsync(
|
||||
"SELECT COUNT(*) FROM UserPermission WHERE UserInfo_UserName = ? AND Permission_Code = ?",
|
||||
new OleDbParameter("@UserName", _selectedUserName),
|
||||
new OleDbParameter("@PermissionCode", permissionCode));
|
||||
|
||||
if (Convert.ToInt32(count) > 0)
|
||||
await Task.Run(() =>
|
||||
{
|
||||
MessageBox.Show($"用户 '{_selectedUserName}' 已拥有权限 '{permissionCode}'!", "警告",
|
||||
MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
// 检查权限是否已存在
|
||||
var count = _accessDataService.ExecuteScalar(
|
||||
"SELECT COUNT(*) FROM UserPermission WHERE UserInfo_UserName = ? AND Permission_Code = ?",
|
||||
new OleDbParameter("@UserName", _selectedUserName),
|
||||
new OleDbParameter("@PermissionCode", permissionCode));
|
||||
|
||||
// 插入权限
|
||||
await _accessDataService.ExecuteAsync(
|
||||
"INSERT INTO UserPermission (UserInfo_UserName, Permission_Code) VALUES (?, ?)",
|
||||
new OleDbParameter("@UserName", _selectedUserName),
|
||||
new OleDbParameter("@PermissionCode", permissionCode));
|
||||
if (Convert.ToInt32(count) > 0)
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
MessageBox.Show($"用户 '{_selectedUserName}' 已拥有权限 '{permissionCode}'!", "警告",
|
||||
MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
_logger?.LogInformation($"成功为用户 {_selectedUserName} 添加权限: {permissionCode}");
|
||||
MessageBox.Show($"成功添加权限 '{permissionCode}'!", "成功",
|
||||
MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
// 插入权限
|
||||
_accessDataService.Execute(
|
||||
"INSERT INTO UserPermission (UserInfo_UserName, Permission_Code) VALUES (?, ?)",
|
||||
new OleDbParameter("@UserName", _selectedUserName),
|
||||
new OleDbParameter("@PermissionCode", permissionCode));
|
||||
|
||||
PermissionCodeTextBox.Clear();
|
||||
LoadUserPermissions(_selectedUserName);
|
||||
_logger?.LogInformation($"成功为用户 {_selectedUserName} 添加权限: {permissionCode}");
|
||||
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
MessageBox.Show($"成功添加权限 '{permissionCode}'!", "成功",
|
||||
MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
|
||||
PermissionCodeTextBox.Clear();
|
||||
LoadUserPermissions(_selectedUserName);
|
||||
});
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -378,16 +418,23 @@ public partial class AccessDatabaseWindowOleDb : Window
|
||||
|
||||
try
|
||||
{
|
||||
await _accessDataService.ExecuteAsync(
|
||||
"DELETE FROM UserPermission WHERE UserInfo_UserName = ? AND Permission_Code = ?",
|
||||
new OleDbParameter("@UserName", _selectedUserName),
|
||||
new OleDbParameter("@PermissionCode", selectedPermission.Permission_Code));
|
||||
await Task.Run(() =>
|
||||
{
|
||||
_accessDataService.Execute(
|
||||
"DELETE FROM UserPermission WHERE UserInfo_UserName = ? AND Permission_Code = ?",
|
||||
new OleDbParameter("@UserName", _selectedUserName),
|
||||
new OleDbParameter("@PermissionCode", selectedPermission.Permission_Code));
|
||||
|
||||
_logger?.LogInformation($"成功删除用户 {_selectedUserName} 的权限: {selectedPermission.Permission_Code}");
|
||||
MessageBox.Show($"成功删除权限 '{selectedPermission.Permission_Code}'!", "成功",
|
||||
MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
_logger?.LogInformation($"成功删除用户 {_selectedUserName} 的权限: {selectedPermission.Permission_Code}");
|
||||
|
||||
LoadUserPermissions(_selectedUserName);
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
MessageBox.Show($"成功删除权限 '{selectedPermission.Permission_Code}'!", "成功",
|
||||
MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
|
||||
LoadUserPermissions(_selectedUserName);
|
||||
});
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -400,8 +447,7 @@ public partial class AccessDatabaseWindowOleDb : Window
|
||||
#endregion
|
||||
|
||||
#region 数据加载
|
||||
|
||||
private async System.Threading.Tasks.Task LoadDataAsync()
|
||||
private void LoadData()
|
||||
{
|
||||
if (_accessDataService == null) return;
|
||||
|
||||
@@ -410,7 +456,7 @@ public partial class AccessDatabaseWindowOleDb : Window
|
||||
_logger?.LogInformation("开始加载 Access 数据库数据...");
|
||||
|
||||
// 查询所有用户(使用泛型映射)
|
||||
var users = await _accessDataService.QueryAsync<UserInfoDto>(
|
||||
var users = _accessDataService.Query<UserInfoDto>(
|
||||
"SELECT [UserName], [Password] AS [Password] FROM [UserInfo]");
|
||||
|
||||
var userDetails = string.Join(" | ",
|
||||
@@ -420,7 +466,7 @@ public partial class AccessDatabaseWindowOleDb : Window
|
||||
// 为每个用户查询权限数量
|
||||
foreach (var user in users)
|
||||
{
|
||||
var count = await _accessDataService.ExecuteScalarAsync(
|
||||
var count = _accessDataService.ExecuteScalar(
|
||||
"SELECT COUNT(*) FROM UserPermission WHERE UserInfo_UserName = ?",
|
||||
new OleDbParameter("@UserName", user.UserName));
|
||||
user.PermissionCount = Convert.ToInt32(count);
|
||||
@@ -428,16 +474,22 @@ public partial class AccessDatabaseWindowOleDb : Window
|
||||
|
||||
_logger?.LogInformation($"加载了 {users.Count} 个用户");
|
||||
|
||||
// 更新 UI
|
||||
UsersDataGrid.ItemsSource = users;
|
||||
PermissionsDataGrid.ItemsSource = null;
|
||||
_selectedUserName = null;
|
||||
// 更新 UI(需要在 UI 线程上执行)
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
UsersDataGrid.ItemsSource = users;
|
||||
PermissionsDataGrid.ItemsSource = null;
|
||||
_selectedUserName = null;
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogError(ex, "加载数据失败");
|
||||
MessageBox.Show($"加载数据失败: {ex.Message}", "错误",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
MessageBox.Show($"加载数据失败: {ex.Message}", "错误",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -457,25 +509,32 @@ public partial class AccessDatabaseWindowOleDb : Window
|
||||
}
|
||||
}
|
||||
|
||||
private async void LoadUserPermissions(string userName)
|
||||
private void LoadUserPermissions(string userName)
|
||||
{
|
||||
if (_accessDataService == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
// 查询用户权限(使用泛型映射)
|
||||
var permissions = await _accessDataService.QueryAsync<UserPermissionDto>(
|
||||
var permissions = _accessDataService.Query<UserPermissionDto>(
|
||||
"SELECT Permission_Code FROM UserPermission WHERE UserInfo_UserName = ?",
|
||||
new OleDbParameter("@UserName", userName));
|
||||
|
||||
PermissionsDataGrid.ItemsSource = permissions;
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
PermissionsDataGrid.ItemsSource = permissions;
|
||||
});
|
||||
|
||||
_logger?.LogInformation($"用户 {userName} 有 {permissions.Count} 个权限");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogError(ex, "加载权限失败");
|
||||
MessageBox.Show($"加载权限失败: {ex.Message}", "错误",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
MessageBox.Show($"加载权限失败: {ex.Message}", "错误",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
},
|
||||
"SharpLog": {
|
||||
"UseConsole": true,
|
||||
"MinLevel": "Information",
|
||||
"MinLevel": "Debug",
|
||||
"File": {
|
||||
"Enabled": true,
|
||||
"Path": "logs/server-.log",
|
||||
|
||||
@@ -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