IOSSServiceManager.cs 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. using OnceMi.AspNetCore.OSS;
  2. using System.Collections.Concurrent;
  3. using Microsoft.Extensions.Configuration;
  4. using Microsoft.Extensions.DependencyInjection;
  5. namespace Admin.NET.Core.Service;
  6. /// <summary>
  7. /// OSS服务管理器接口
  8. /// </summary>
  9. public interface IOSSServiceManager : IDisposable
  10. {
  11. /// <summary>
  12. /// 获取OSS服务实例
  13. /// </summary>
  14. /// <param name="provider">存储提供者配置</param>
  15. /// <returns></returns>
  16. Task<IOSSService> GetOSSServiceAsync(SysFileProvider provider);
  17. /// <summary>
  18. /// 清除缓存
  19. /// </summary>
  20. void ClearCache();
  21. }
  22. /// <summary>
  23. /// OSS服务管理器实现
  24. /// </summary>
  25. public class OSSServiceManager : IOSSServiceManager, ITransient
  26. {
  27. private readonly IServiceProvider _serviceProvider;
  28. private readonly ConcurrentDictionary<string, IOSSService> _ossServiceCache;
  29. private readonly object _lockObject = new object();
  30. private bool _disposed = false;
  31. public OSSServiceManager(IServiceProvider serviceProvider)
  32. {
  33. _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
  34. _ossServiceCache = new ConcurrentDictionary<string, IOSSService>();
  35. }
  36. /// <summary>
  37. /// 获取OSS服务实例(带缓存)
  38. /// </summary>
  39. /// <param name="provider">存储提供者配置</param>
  40. /// <returns></returns>
  41. public async Task<IOSSService> GetOSSServiceAsync(SysFileProvider provider)
  42. {
  43. if (provider == null)
  44. throw new ArgumentNullException(nameof(provider));
  45. var cacheKey = provider.ConfigKey;
  46. // 尝试从缓存获取
  47. if (_ossServiceCache.TryGetValue(cacheKey, out var cachedService))
  48. {
  49. return cachedService;
  50. }
  51. // 验证配置
  52. if (!await ValidateConfigurationAsync(provider))
  53. {
  54. throw new InvalidOperationException($"OSS提供者配置无效: {provider.DisplayName}");
  55. }
  56. // 线程安全地创建新服务
  57. lock (_lockObject)
  58. {
  59. // 双重检查锁定模式
  60. if (_ossServiceCache.TryGetValue(cacheKey, out cachedService))
  61. {
  62. return cachedService;
  63. }
  64. // 转换配置并创建服务
  65. var ossOptions = ConvertToOSSOptions(provider);
  66. var ossService = CreateOSSService(ossOptions);
  67. // 添加到缓存
  68. _ossServiceCache.TryAdd(cacheKey, ossService);
  69. return ossService;
  70. }
  71. }
  72. /// <summary>
  73. /// 创建OSS服务实例
  74. /// </summary>
  75. /// <param name="options">OSS配置选项</param>
  76. /// <returns></returns>
  77. private IOSSService CreateOSSService(OSSOptions options)
  78. {
  79. ArgumentNullException.ThrowIfNull(options);
  80. try
  81. {
  82. // 使用现有的IOSSServiceFactory,但需要先注册配置
  83. var providerName = Enum.GetName(options.Provider);
  84. var configSectionName = $"TempOSS_{Guid.NewGuid():N}";
  85. // 创建临时配置
  86. var configData = new Dictionary<string, string>
  87. {
  88. [$"{configSectionName}:Provider"] = providerName,
  89. [$"{configSectionName}:Endpoint"] = options.Endpoint ?? "",
  90. [$"{configSectionName}:AccessKey"] = options.AccessKey ?? "",
  91. [$"{configSectionName}:SecretKey"] = options.SecretKey ?? "",
  92. [$"{configSectionName}:Region"] = options.Region ?? "",
  93. [$"{configSectionName}:IsEnableHttps"] = options.IsEnableHttps.ToString(),
  94. [$"{configSectionName}:IsEnableCache"] = options.IsEnableCache.ToString()
  95. };
  96. var tempConfig = new ConfigurationBuilder()
  97. .AddInMemoryCollection(configData)
  98. .Build();
  99. // 创建临时服务集合,但不立即释放
  100. var services = new ServiceCollection();
  101. services.AddSingleton<IConfiguration>(tempConfig);
  102. services.AddLogging();
  103. services.AddOSSService(providerName, configSectionName);
  104. // 构建服务提供者并创建OSS服务
  105. var tempServiceProvider = services.BuildServiceProvider();
  106. var ossServiceFactory = tempServiceProvider.GetRequiredService<IOSSServiceFactory>();
  107. var ossService = ossServiceFactory.Create(providerName);
  108. // 注意:不要释放tempServiceProvider,因为ossService可能依赖它
  109. // 这里我们接受这个内存开销,因为缓存会减少创建频率
  110. return ossService;
  111. }
  112. catch (Exception ex)
  113. {
  114. throw Oops.Oh($"创建OSS服务失败: {ex.Message}");
  115. }
  116. }
  117. /// <summary>
  118. /// 验证配置
  119. /// </summary>
  120. /// <param name="provider">存储提供者配置</param>
  121. /// <returns></returns>
  122. private Task<bool> ValidateConfigurationAsync(SysFileProvider provider)
  123. {
  124. if (provider == null) return Task.FromResult(false);
  125. // 基本字段验证
  126. var isValid = !string.IsNullOrWhiteSpace(provider.Provider) &&
  127. !string.IsNullOrWhiteSpace(provider.BucketName) &&
  128. !string.IsNullOrWhiteSpace(provider.AccessKey) &&
  129. !string.IsNullOrWhiteSpace(provider.SecretKey);
  130. // Minio额外需要Endpoint
  131. if (provider.Provider.ToUpper() == "MINIO")
  132. {
  133. isValid = isValid && !string.IsNullOrWhiteSpace(provider.Endpoint);
  134. }
  135. return Task.FromResult(isValid);
  136. }
  137. /// <summary>
  138. /// 将SysFileProvider转换为OSSOptions
  139. /// </summary>
  140. /// <param name="provider"></param>
  141. /// <returns></returns>
  142. private OSSOptions ConvertToOSSOptions(SysFileProvider provider)
  143. {
  144. if (provider == null)
  145. throw new ArgumentNullException(nameof(provider));
  146. var ossOptions = new OSSOptions
  147. {
  148. Provider = Enum.Parse<OSSProvider>(provider.Provider),
  149. Endpoint = provider.Endpoint,
  150. Region = provider.Region,
  151. IsEnableHttps = provider.IsEnableHttps ?? true,
  152. IsEnableCache = provider.IsEnableCache ?? true
  153. };
  154. // 设置认证信息(所有提供者现在都使用统一的字段)
  155. ossOptions.AccessKey = provider.AccessKey;
  156. ossOptions.SecretKey = provider.SecretKey;
  157. return ossOptions;
  158. }
  159. /// <summary>
  160. /// 清除缓存
  161. /// </summary>
  162. public void ClearCache()
  163. {
  164. lock (_lockObject)
  165. {
  166. _ossServiceCache.Clear();
  167. }
  168. }
  169. /// <summary>
  170. /// 释放资源
  171. /// </summary>
  172. public void Dispose()
  173. {
  174. if (!_disposed)
  175. {
  176. lock (_lockObject)
  177. {
  178. _ossServiceCache.Clear();
  179. }
  180. _disposed = true;
  181. }
  182. }
  183. }