DatabaseManager.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Reflection;
  6. using System.Threading.Tasks;
  7. using Newtonsoft.Json;
  8. using TeamAAS.Database.Attributes;
  9. using TeamAAS.Database.Interfaces;
  10. using TeamAAS.Database.Models;
  11. namespace TeamAAS.Database
  12. {
  13. /// <summary>
  14. /// 数据库管理器。
  15. /// 负责多数据库实例的创建、注册、移除、配置持久化以及提供者反射发现。
  16. /// 懒汉单例模式:DatabaseManager.Instance
  17. /// </summary>
  18. public class DatabaseManager : IDatabaseManager
  19. {
  20. #region 单例
  21. private static readonly Lazy<DatabaseManager> _instance =
  22. new Lazy<DatabaseManager>(() => new DatabaseManager(), isThreadSafe: true);
  23. /// <summary>
  24. /// 懒汉单例入口。首次访问时初始化,线程安全。
  25. /// </summary>
  26. public static DatabaseManager Instance => _instance.Value;
  27. #endregion
  28. #region 字段
  29. private readonly Dictionary<Guid, IDatabase> _databases;
  30. private readonly object _sync = new object();
  31. // 反射扫描的提供者缓存
  32. private List<DatabaseProviderInfo> _providers;
  33. private readonly object _providerLock = new object();
  34. // 配置文件路径
  35. private string _configPath;
  36. #endregion
  37. #region 构造
  38. public DatabaseManager()
  39. {
  40. _databases = new Dictionary<Guid, IDatabase>();
  41. }
  42. #endregion
  43. #region 提供者发现
  44. /// <summary>
  45. /// 获取所有可用的数据库提供者(反射扫描当前程序集及已加载程序集中的 IDatabase 实现)
  46. /// </summary>
  47. public List<DatabaseProviderInfo> GetAvailableProviders()
  48. {
  49. lock (_providerLock)
  50. {
  51. if (_providers != null)
  52. return _providers.ToList();
  53. _providers = new List<DatabaseProviderInfo>();
  54. try
  55. {
  56. // 反射扫描:收集全部 IDatabase 实现(当前程序集 + 同级目录下的 DLL)
  57. foreach (var type in ScanImplementations<IDatabase>())
  58. {
  59. var attr = type.GetCustomAttribute<DatabaseProviderAttribute>();
  60. if (attr == null)
  61. continue;
  62. _providers.Add(new DatabaseProviderInfo
  63. {
  64. ProviderType = attr.ProviderType,
  65. DisplayName = attr.DisplayName,
  66. ImplementationType = type,
  67. RequiresServer = attr.RequiresServer,
  68. Description = attr.Description
  69. });
  70. }
  71. }
  72. catch { /* 提供者扫描失败不抛异常 */ }
  73. return _providers.ToList();
  74. }
  75. }
  76. /// <summary>
  77. /// 根据 providerType 创建数据库实例
  78. /// </summary>
  79. private IDatabase CreateDatabaseInstance(DatabaseConfig config)
  80. {
  81. var providers = GetAvailableProviders();
  82. var provider = providers.FirstOrDefault(
  83. p => string.Equals(p.ProviderType, config.ProviderType, StringComparison.OrdinalIgnoreCase));
  84. if (provider == null)
  85. throw new InvalidOperationException($"未找到数据库提供者:{config.ProviderType}");
  86. var instance = (IDatabase)Activator.CreateInstance(provider.ImplementationType);
  87. instance.Configure(config);
  88. return instance;
  89. }
  90. #endregion
  91. #region 反射扫描辅助
  92. /// <summary>
  93. /// 扫描当前程序集及应用程序基目录下所有 DLL,查找实现了 TInterface 的非抽象类。
  94. /// </summary>
  95. private static List<Type> ScanImplementations<TInterface>()
  96. {
  97. var result = new List<Type>();
  98. var interfaceType = typeof(TInterface);
  99. // 1. 当前程序集
  100. var currentAsm = Assembly.GetExecutingAssembly();
  101. ScanAssembly(currentAsm, interfaceType, result);
  102. // 2. 应用程序基目录下的其他 DLL(支持插件扩展)
  103. try
  104. {
  105. string baseDir = AppDomain.CurrentDomain.BaseDirectory;
  106. foreach (var dll in Directory.GetFiles(baseDir, "*.dll", SearchOption.TopDirectoryOnly))
  107. {
  108. try
  109. {
  110. var asmName = AssemblyName.GetAssemblyName(dll);
  111. if (asmName.FullName == currentAsm.FullName)
  112. continue; // 已扫描过
  113. var asm = Assembly.Load(asmName);
  114. ScanAssembly(asm, interfaceType, result);
  115. }
  116. catch { /* 单个 DLL 加载失败跳过 */ }
  117. }
  118. }
  119. catch { /* 目录扫描失败跳过 */ }
  120. return result;
  121. }
  122. private static void ScanAssembly(Assembly asm, Type interfaceType, List<Type> result)
  123. {
  124. try
  125. {
  126. foreach (var type in asm.GetTypes())
  127. {
  128. if (type.IsAbstract || type.IsInterface || !type.IsClass)
  129. continue;
  130. if (!interfaceType.IsAssignableFrom(type))
  131. continue;
  132. result.Add(type);
  133. }
  134. }
  135. catch { /* 反射获取类型失败跳过 */ }
  136. }
  137. #endregion
  138. #region 数据库生命周期
  139. public bool CreateDatabase(Guid id, DatabaseConfig config)
  140. {
  141. if (config == null) throw new ArgumentNullException(nameof(config));
  142. if (string.IsNullOrWhiteSpace(config.ProviderType))
  143. throw new ArgumentException("ProviderType 不能为空", nameof(config));
  144. var database = CreateDatabaseInstance(config);
  145. IDatabase existing = null;
  146. lock (_sync)
  147. {
  148. _databases.TryGetValue(id, out existing);
  149. _databases[id] = database;
  150. }
  151. if (existing != null && !ReferenceEquals(existing, database))
  152. {
  153. try { existing.Dispose(); } catch { }
  154. }
  155. return true;
  156. }
  157. public Task<bool> CreateDatabaseAsync(Guid id, DatabaseConfig config)
  158. {
  159. return Task.Run(() => CreateDatabase(id, config));
  160. }
  161. public IDatabase GetDatabase(Guid id)
  162. {
  163. lock (_sync)
  164. {
  165. return _databases.TryGetValue(id, out var db) ? db : null;
  166. }
  167. }
  168. public bool TryGetDatabase(Guid id, out IDatabase database)
  169. {
  170. lock (_sync)
  171. {
  172. return _databases.TryGetValue(id, out database);
  173. }
  174. }
  175. public bool ContainsDatabase(Guid id)
  176. {
  177. lock (_sync)
  178. {
  179. return _databases.ContainsKey(id);
  180. }
  181. }
  182. public IReadOnlyCollection<IDatabase> GetAllDatabases()
  183. {
  184. lock (_sync)
  185. {
  186. return _databases.Values.ToList().AsReadOnly();
  187. }
  188. }
  189. public bool RemoveDatabase(Guid id)
  190. {
  191. IDatabase db = null;
  192. lock (_sync)
  193. {
  194. if (_databases.TryGetValue(id, out db))
  195. _databases.Remove(id);
  196. }
  197. if (db != null)
  198. {
  199. try { db.Dispose(); } catch { }
  200. return true;
  201. }
  202. return false;
  203. }
  204. public Task<bool> RemoveDatabaseAsync(Guid id)
  205. {
  206. return Task.Run(() => RemoveDatabase(id));
  207. }
  208. public void RemoveAllDatabases()
  209. {
  210. List<IDatabase> all;
  211. lock (_sync)
  212. {
  213. all = _databases.Values.ToList();
  214. _databases.Clear();
  215. }
  216. foreach (var db in all)
  217. {
  218. try { db.Dispose(); } catch { }
  219. }
  220. }
  221. public (bool IsSucceed, string Message) InitializeAllDatabases(DatabaseConfig[] configs)
  222. {
  223. if (configs == null) return (true, "无配置");
  224. var errors = new List<string>();
  225. var successCount = 0;
  226. foreach (var config in configs)
  227. {
  228. try
  229. {
  230. var id = config.Id == Guid.Empty ? Guid.NewGuid() : config.Id;
  231. CreateDatabase(id, config);
  232. successCount++;
  233. }
  234. catch (Exception ex)
  235. {
  236. errors.Add($"{config.Name ?? config.ProviderType}: {ex.Message}");
  237. }
  238. }
  239. if (errors.Count == 0)
  240. return (true, $"成功初始化 {successCount} 个数据库");
  241. return (false, $"成功 {successCount} 个,失败 {errors.Count} 个:{string.Join("; ", errors)}");
  242. }
  243. public Task<(bool IsSucceed, string Message)> InitializeAllDatabasesAsync(DatabaseConfig[] configs)
  244. {
  245. return Task.Run(() => InitializeAllDatabases(configs));
  246. }
  247. public IReadOnlyList<DatabaseConfig> GetAllDatabaseConfigs()
  248. {
  249. var result = new List<DatabaseConfig>();
  250. lock (_sync)
  251. {
  252. foreach (var db in _databases.Values)
  253. {
  254. result.Add(new DatabaseConfig
  255. {
  256. Id = db.Id,
  257. Name = db.Name,
  258. ProviderType = db.ProviderType,
  259. ConnectionString = db.ConnectionString
  260. });
  261. }
  262. }
  263. return result.AsReadOnly();
  264. }
  265. #endregion
  266. #region 配置持久化
  267. /// <summary>
  268. /// 保存配置到 JSON 文件
  269. /// </summary>
  270. public bool SaveConfig(string path = null)
  271. {
  272. try
  273. {
  274. var filePath = path ?? _configPath;
  275. if (string.IsNullOrWhiteSpace(filePath))
  276. throw new ArgumentException("配置文件路径不能为空", nameof(path));
  277. _configPath = filePath;
  278. var configs = GetAllDatabaseConfigs();
  279. var json = JsonConvert.SerializeObject(configs, Formatting.Indented);
  280. var dir = Path.GetDirectoryName(filePath);
  281. if (!string.IsNullOrWhiteSpace(dir) && !Directory.Exists(dir))
  282. Directory.CreateDirectory(dir);
  283. File.WriteAllText(filePath, json);
  284. return true;
  285. }
  286. catch
  287. {
  288. return false;
  289. }
  290. }
  291. /// <summary>
  292. /// 从 JSON 文件加载配置并初始化
  293. /// </summary>
  294. public (bool IsSucceed, string Message) LoadConfig(string path)
  295. {
  296. try
  297. {
  298. if (!File.Exists(path))
  299. return (false, $"配置文件不存在:{path}");
  300. _configPath = path;
  301. var json = File.ReadAllText(path);
  302. var configs = JsonConvert.DeserializeObject<DatabaseConfig[]>(json);
  303. if (configs == null || configs.Length == 0)
  304. return (true, "配置为空");
  305. return InitializeAllDatabases(configs);
  306. }
  307. catch (Exception ex)
  308. {
  309. return (false, $"加载配置失败:{ex.Message}");
  310. }
  311. }
  312. #endregion
  313. #region IDisposable
  314. private bool _disposed;
  315. public void Dispose()
  316. {
  317. Dispose(true);
  318. GC.SuppressFinalize(this);
  319. }
  320. protected virtual void Dispose(bool disposing)
  321. {
  322. if (_disposed) return;
  323. if (disposing)
  324. {
  325. RemoveAllDatabases();
  326. }
  327. _disposed = true;
  328. }
  329. ~DatabaseManager()
  330. {
  331. Dispose(false);
  332. }
  333. #endregion
  334. }
  335. }