| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408 |
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
- using System.Reflection;
- using System.Threading.Tasks;
- using Newtonsoft.Json;
- using TeamAAS.Database.Attributes;
- using TeamAAS.Database.Interfaces;
- using TeamAAS.Database.Models;
- namespace TeamAAS.Database
- {
- /// <summary>
- /// 数据库管理器。
- /// 负责多数据库实例的创建、注册、移除、配置持久化以及提供者反射发现。
- /// 懒汉单例模式:DatabaseManager.Instance
- /// </summary>
- public class DatabaseManager : IDatabaseManager
- {
- #region 单例
- private static readonly Lazy<DatabaseManager> _instance =
- new Lazy<DatabaseManager>(() => new DatabaseManager(), isThreadSafe: true);
- /// <summary>
- /// 懒汉单例入口。首次访问时初始化,线程安全。
- /// </summary>
- public static DatabaseManager Instance => _instance.Value;
- #endregion
- #region 字段
- private readonly Dictionary<Guid, IDatabase> _databases;
- private readonly object _sync = new object();
- // 反射扫描的提供者缓存
- private List<DatabaseProviderInfo> _providers;
- private readonly object _providerLock = new object();
- // 配置文件路径
- private string _configPath;
- #endregion
- #region 构造
- public DatabaseManager()
- {
- _databases = new Dictionary<Guid, IDatabase>();
- }
- #endregion
- #region 提供者发现
- /// <summary>
- /// 获取所有可用的数据库提供者(反射扫描当前程序集及已加载程序集中的 IDatabase 实现)
- /// </summary>
- public List<DatabaseProviderInfo> GetAvailableProviders()
- {
- lock (_providerLock)
- {
- if (_providers != null)
- return _providers.ToList();
- _providers = new List<DatabaseProviderInfo>();
- try
- {
- // 反射扫描:收集全部 IDatabase 实现(当前程序集 + 同级目录下的 DLL)
- foreach (var type in ScanImplementations<IDatabase>())
- {
- var attr = type.GetCustomAttribute<DatabaseProviderAttribute>();
- if (attr == null)
- continue;
- _providers.Add(new DatabaseProviderInfo
- {
- ProviderType = attr.ProviderType,
- DisplayName = attr.DisplayName,
- ImplementationType = type,
- RequiresServer = attr.RequiresServer,
- Description = attr.Description
- });
- }
- }
- catch { /* 提供者扫描失败不抛异常 */ }
- return _providers.ToList();
- }
- }
- /// <summary>
- /// 根据 providerType 创建数据库实例
- /// </summary>
- private IDatabase CreateDatabaseInstance(DatabaseConfig config)
- {
- var providers = GetAvailableProviders();
- var provider = providers.FirstOrDefault(
- p => string.Equals(p.ProviderType, config.ProviderType, StringComparison.OrdinalIgnoreCase));
- if (provider == null)
- throw new InvalidOperationException($"未找到数据库提供者:{config.ProviderType}");
- var instance = (IDatabase)Activator.CreateInstance(provider.ImplementationType);
- instance.Configure(config);
- return instance;
- }
- #endregion
- #region 反射扫描辅助
- /// <summary>
- /// 扫描当前程序集及应用程序基目录下所有 DLL,查找实现了 TInterface 的非抽象类。
- /// </summary>
- private static List<Type> ScanImplementations<TInterface>()
- {
- var result = new List<Type>();
- var interfaceType = typeof(TInterface);
- // 1. 当前程序集
- var currentAsm = Assembly.GetExecutingAssembly();
- ScanAssembly(currentAsm, interfaceType, result);
- // 2. 应用程序基目录下的其他 DLL(支持插件扩展)
- try
- {
- string baseDir = AppDomain.CurrentDomain.BaseDirectory;
- foreach (var dll in Directory.GetFiles(baseDir, "*.dll", SearchOption.TopDirectoryOnly))
- {
- try
- {
- var asmName = AssemblyName.GetAssemblyName(dll);
- if (asmName.FullName == currentAsm.FullName)
- continue; // 已扫描过
- var asm = Assembly.Load(asmName);
- ScanAssembly(asm, interfaceType, result);
- }
- catch { /* 单个 DLL 加载失败跳过 */ }
- }
- }
- catch { /* 目录扫描失败跳过 */ }
- return result;
- }
- private static void ScanAssembly(Assembly asm, Type interfaceType, List<Type> result)
- {
- try
- {
- foreach (var type in asm.GetTypes())
- {
- if (type.IsAbstract || type.IsInterface || !type.IsClass)
- continue;
- if (!interfaceType.IsAssignableFrom(type))
- continue;
- result.Add(type);
- }
- }
- catch { /* 反射获取类型失败跳过 */ }
- }
- #endregion
- #region 数据库生命周期
- public bool CreateDatabase(Guid id, DatabaseConfig config)
- {
- if (config == null) throw new ArgumentNullException(nameof(config));
- if (string.IsNullOrWhiteSpace(config.ProviderType))
- throw new ArgumentException("ProviderType 不能为空", nameof(config));
- var database = CreateDatabaseInstance(config);
- IDatabase existing = null;
- lock (_sync)
- {
- _databases.TryGetValue(id, out existing);
- _databases[id] = database;
- }
- if (existing != null && !ReferenceEquals(existing, database))
- {
- try { existing.Dispose(); } catch { }
- }
- return true;
- }
- public Task<bool> CreateDatabaseAsync(Guid id, DatabaseConfig config)
- {
- return Task.Run(() => CreateDatabase(id, config));
- }
- public IDatabase GetDatabase(Guid id)
- {
- lock (_sync)
- {
- return _databases.TryGetValue(id, out var db) ? db : null;
- }
- }
- public bool TryGetDatabase(Guid id, out IDatabase database)
- {
- lock (_sync)
- {
- return _databases.TryGetValue(id, out database);
- }
- }
- public bool ContainsDatabase(Guid id)
- {
- lock (_sync)
- {
- return _databases.ContainsKey(id);
- }
- }
- public IReadOnlyCollection<IDatabase> GetAllDatabases()
- {
- lock (_sync)
- {
- return _databases.Values.ToList().AsReadOnly();
- }
- }
- public bool RemoveDatabase(Guid id)
- {
- IDatabase db = null;
- lock (_sync)
- {
- if (_databases.TryGetValue(id, out db))
- _databases.Remove(id);
- }
- if (db != null)
- {
- try { db.Dispose(); } catch { }
- return true;
- }
- return false;
- }
- public Task<bool> RemoveDatabaseAsync(Guid id)
- {
- return Task.Run(() => RemoveDatabase(id));
- }
- public void RemoveAllDatabases()
- {
- List<IDatabase> all;
- lock (_sync)
- {
- all = _databases.Values.ToList();
- _databases.Clear();
- }
- foreach (var db in all)
- {
- try { db.Dispose(); } catch { }
- }
- }
- public (bool IsSucceed, string Message) InitializeAllDatabases(DatabaseConfig[] configs)
- {
- if (configs == null) return (true, "无配置");
- var errors = new List<string>();
- var successCount = 0;
- foreach (var config in configs)
- {
- try
- {
- var id = config.Id == Guid.Empty ? Guid.NewGuid() : config.Id;
- CreateDatabase(id, config);
- successCount++;
- }
- catch (Exception ex)
- {
- errors.Add($"{config.Name ?? config.ProviderType}: {ex.Message}");
- }
- }
- if (errors.Count == 0)
- return (true, $"成功初始化 {successCount} 个数据库");
- return (false, $"成功 {successCount} 个,失败 {errors.Count} 个:{string.Join("; ", errors)}");
- }
- public Task<(bool IsSucceed, string Message)> InitializeAllDatabasesAsync(DatabaseConfig[] configs)
- {
- return Task.Run(() => InitializeAllDatabases(configs));
- }
- public IReadOnlyList<DatabaseConfig> GetAllDatabaseConfigs()
- {
- var result = new List<DatabaseConfig>();
- lock (_sync)
- {
- foreach (var db in _databases.Values)
- {
- result.Add(new DatabaseConfig
- {
- Id = db.Id,
- Name = db.Name,
- ProviderType = db.ProviderType,
- ConnectionString = db.ConnectionString
- });
- }
- }
- return result.AsReadOnly();
- }
- #endregion
- #region 配置持久化
- /// <summary>
- /// 保存配置到 JSON 文件
- /// </summary>
- public bool SaveConfig(string path = null)
- {
- try
- {
- var filePath = path ?? _configPath;
- if (string.IsNullOrWhiteSpace(filePath))
- throw new ArgumentException("配置文件路径不能为空", nameof(path));
- _configPath = filePath;
- var configs = GetAllDatabaseConfigs();
- var json = JsonConvert.SerializeObject(configs, Formatting.Indented);
- var dir = Path.GetDirectoryName(filePath);
- if (!string.IsNullOrWhiteSpace(dir) && !Directory.Exists(dir))
- Directory.CreateDirectory(dir);
- File.WriteAllText(filePath, json);
- return true;
- }
- catch
- {
- return false;
- }
- }
- /// <summary>
- /// 从 JSON 文件加载配置并初始化
- /// </summary>
- public (bool IsSucceed, string Message) LoadConfig(string path)
- {
- try
- {
- if (!File.Exists(path))
- return (false, $"配置文件不存在:{path}");
- _configPath = path;
- var json = File.ReadAllText(path);
- var configs = JsonConvert.DeserializeObject<DatabaseConfig[]>(json);
- if (configs == null || configs.Length == 0)
- return (true, "配置为空");
- return InitializeAllDatabases(configs);
- }
- catch (Exception ex)
- {
- return (false, $"加载配置失败:{ex.Message}");
- }
- }
- #endregion
- #region IDisposable
- private bool _disposed;
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
- protected virtual void Dispose(bool disposing)
- {
- if (_disposed) return;
- if (disposing)
- {
- RemoveAllDatabases();
- }
- _disposed = true;
- }
- ~DatabaseManager()
- {
- Dispose(false);
- }
- #endregion
- }
- }
|