From 9243a26189f14352bbafa2df50d3ea2dbeef7dde Mon Sep 17 00:00:00 2001 From: ShiYu Date: Thu, 22 May 2025 16:31:32 +0800 Subject: [PATCH] feat(storage): refactor storage service and provider interface, support dynamic registration of storage providers --- Extensions/ServiceCollectionExtensions.cs | 4 +- Models/DataBase/Picture.cs | 9 +- .../Request/Picture/UploadPictureRequest.cs | 1 + .../Attributes/StorageProviderAttribute.cs | 33 ++++++ Services/BackgroundTaskQueue.cs | 15 +-- Services/Interface/IPictureService.cs | 1 + Services/Interface/IStorageProviderFactory.cs | 14 --- Services/Interface/IStorageService.cs | 50 +++++++++ Services/PictureService.cs | 40 +++---- .../StorageProvider/CosStorageProvider.cs | 24 ++-- .../StorageProvider/LocalStorageProvider.cs | 2 + Services/StorageProvider/S3StorageProvider.cs | 10 +- .../TelegramStorageProvider.cs | 8 +- Services/StorageProviderFactory.cs | 25 ----- Services/StorageService.cs | 106 ++++++++++++++++++ 15 files changed, 237 insertions(+), 105 deletions(-) create mode 100644 Services/Attributes/StorageProviderAttribute.cs delete mode 100644 Services/Interface/IStorageProviderFactory.cs create mode 100644 Services/Interface/IStorageService.cs delete mode 100644 Services/StorageProviderFactory.cs create mode 100644 Services/StorageService.cs diff --git a/Extensions/ServiceCollectionExtensions.cs b/Extensions/ServiceCollectionExtensions.cs index 353203c..282c93c 100644 --- a/Extensions/ServiceCollectionExtensions.cs +++ b/Extensions/ServiceCollectionExtensions.cs @@ -5,7 +5,9 @@ using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Tokens; using System.Text; +using Foxel.Services.Attributes; using Foxel.Services.StorageProvider; +using System.Reflection; namespace Foxel.Extensions; @@ -25,7 +27,7 @@ public static class ServiceCollectionExtensions services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); } diff --git a/Models/DataBase/Picture.cs b/Models/DataBase/Picture.cs index 63ec599..6b09011 100644 --- a/Models/DataBase/Picture.cs +++ b/Models/DataBase/Picture.cs @@ -1,18 +1,11 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json; +using Foxel.Services.Attributes; using Vector = Pgvector.Vector; namespace Foxel.Models.DataBase; -public enum StorageType -{ - Local = 0, - Telegram = 1, - S3 = 2, - Cos = 3, -} - public class Picture : BaseModel { [StringLength(255)] public string Name { get; set; } = string.Empty; diff --git a/Models/Request/Picture/UploadPictureRequest.cs b/Models/Request/Picture/UploadPictureRequest.cs index ce2ce4b..f304b9e 100644 --- a/Models/Request/Picture/UploadPictureRequest.cs +++ b/Models/Request/Picture/UploadPictureRequest.cs @@ -1,5 +1,6 @@ using System.ComponentModel.DataAnnotations; using Foxel.Models.DataBase; +using Foxel.Services.Attributes; namespace Foxel.Models.Request.Picture; diff --git a/Services/Attributes/StorageProviderAttribute.cs b/Services/Attributes/StorageProviderAttribute.cs new file mode 100644 index 0000000..655b857 --- /dev/null +++ b/Services/Attributes/StorageProviderAttribute.cs @@ -0,0 +1,33 @@ +using Foxel.Models.DataBase; + +namespace Foxel.Services.Attributes; + +public enum StorageType +{ + Local = 0, + Telegram = 1, + S3 = 2, + Cos = 3, +} + + +/// +/// 标记存储提供者类的特性 +/// +[AttributeUsage(AttributeTargets.Class)] +public class StorageProviderAttribute : Attribute +{ + /// + /// 存储类型 + /// + public StorageType StorageType { get; } + + /// + /// 构造函数 + /// + /// 存储类型 + public StorageProviderAttribute(StorageType storageType) + { + StorageType = storageType; + } +} diff --git a/Services/BackgroundTaskQueue.cs b/Services/BackgroundTaskQueue.cs index dd69cea..da7209e 100644 --- a/Services/BackgroundTaskQueue.cs +++ b/Services/BackgroundTaskQueue.cs @@ -2,9 +2,9 @@ using System.Collections.Concurrent; using System.Threading.Channels; using Microsoft.EntityFrameworkCore; using Foxel.Models.DataBase; +using Foxel.Services.Attributes; using Foxel.Services.Interface; using Foxel.Utils; -using Foxel.Services.StorageProvider; namespace Foxel.Services; @@ -200,9 +200,8 @@ public sealed class BackgroundTaskQueue : IBackgroundTaskQueue, IDisposable try { using var scope = _serviceProvider.CreateScope(); - var pictureService = scope.ServiceProvider.GetRequiredService(); var aiService = scope.ServiceProvider.GetRequiredService(); - var storageProviderFactory = scope.ServiceProvider.GetRequiredService(); + var storageService = scope.ServiceProvider.GetRequiredService(); // 1. 获取图片信息 await UpdatePictureStatus(task.PictureId, ProcessingStatus.Processing, 10); @@ -214,9 +213,6 @@ public sealed class BackgroundTaskQueue : IBackgroundTaskQueue, IDisposable throw new Exception($"找不到ID为{task.PictureId}的图片"); } - // 根据存储类型获取文件处理路径 - var storageProvider = storageProviderFactory.GetProvider(picture.StorageType); - // 处理文件获取逻辑 if (picture.StorageType == StorageType.Local) { @@ -227,7 +223,7 @@ public sealed class BackgroundTaskQueue : IBackgroundTaskQueue, IDisposable { // 非本地存储需要先下载文件 await UpdatePictureStatus(task.PictureId, ProcessingStatus.Processing, 15); - localFilePath = await storageProvider.DownloadFileAsync(picture.Path); + localFilePath = await storageService.DownloadFileAsync(picture.StorageType, picture.Path); isTempFile = true; } @@ -257,12 +253,13 @@ public sealed class BackgroundTaskQueue : IBackgroundTaskQueue, IDisposable else { // 非本地存储,上传缩略图到对应的存储服务 - using var thumbnailFileStream = new FileStream(thumbnailPath, FileMode.Open, FileAccess.Read); + await using var thumbnailFileStream = new FileStream(thumbnailPath, FileMode.Open, FileAccess.Read); var thumbnailFileName = Path.GetFileName(thumbnailPath); var thumbnailContentType = Path.GetExtension(thumbnailPath).ToLower() == ".png" ? "image/png" : "image/jpeg"; // 上传缩略图并获取存储路径或元数据 - string thumbnailStoragePath = await storageProvider.SaveAsync( + string thumbnailStoragePath = await storageService.SaveAsync( + picture.StorageType, thumbnailFileStream, thumbnailFileName, thumbnailContentType); diff --git a/Services/Interface/IPictureService.cs b/Services/Interface/IPictureService.cs index 8254271..e21b4a2 100644 --- a/Services/Interface/IPictureService.cs +++ b/Services/Interface/IPictureService.cs @@ -1,6 +1,7 @@ using Foxel.Models; using Foxel.Models.DataBase; using Foxel.Models.Response.Picture; +using Foxel.Services.Attributes; namespace Foxel.Services.Interface; diff --git a/Services/Interface/IStorageProviderFactory.cs b/Services/Interface/IStorageProviderFactory.cs deleted file mode 100644 index 4fb18dc..0000000 --- a/Services/Interface/IStorageProviderFactory.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Foxel.Models.DataBase; -using Foxel.Services.Interface; - -namespace Foxel.Services.Interface; - -public interface IStorageProviderFactory -{ - /// - /// 根据存储类型获取对应的存储提供者 - /// - /// 存储类型 - /// 存储提供者实例 - IStorageProvider GetProvider(StorageType storageType); -} diff --git a/Services/Interface/IStorageService.cs b/Services/Interface/IStorageService.cs new file mode 100644 index 0000000..eee5e55 --- /dev/null +++ b/Services/Interface/IStorageService.cs @@ -0,0 +1,50 @@ +using Foxel.Models.DataBase; +using Foxel.Services.Attributes; + +namespace Foxel.Services.Interface; + +/// +/// 统一的存储服务接口 +/// +public interface IStorageService +{ + /// + /// 根据存储类型获取对应的存储提供者 + /// + /// 存储类型 + /// 存储提供者实例 + IStorageProvider GetProvider(StorageType storageType); + + /// + /// 使用指定存储类型保存文件 + /// + /// 存储类型 + /// 文件流 + /// 文件名 + /// 内容类型 + /// 存储路径 + Task SaveAsync(StorageType storageType, Stream fileStream, string fileName, string contentType); + + /// + /// 使用指定存储类型删除文件 + /// + /// 存储类型 + /// 存储路径 + Task DeleteAsync(StorageType storageType, string storagePath); + + /// + /// 使用指定存储类型获取文件URL + /// + /// 存储类型 + /// 存储路径 + /// 文件URL + string GetUrl(StorageType storageType, string storagePath); + + /// + /// 使用指定存储类型下载文件 + /// + /// 存储类型 + /// 存储路径 + /// 本地文件路径 + Task DownloadFileAsync(StorageType storageType, string storagePath); +} diff --git a/Services/PictureService.cs b/Services/PictureService.cs index 89e8436..68b5b15 100644 --- a/Services/PictureService.cs +++ b/Services/PictureService.cs @@ -7,6 +7,7 @@ using Pgvector; using Pgvector.EntityFrameworkCore; using System.Text.Json; using Foxel.Models.Response.Picture; +using Foxel.Services.Attributes; namespace Foxel.Services; @@ -15,7 +16,7 @@ public class PictureService( IAiService embeddingService, IConfigService configuration, IBackgroundTaskQueue backgroundTaskQueue, - IStorageProviderFactory storageProviderFactory) + IStorageService storageService) : IPictureService { private readonly string _serverUrl = configuration["AppSettings:ServerUrl"]; @@ -334,14 +335,12 @@ public class PictureService( // 将数据库实体映射到响应对象 private PictureResponse MapPictureToResponse(Picture picture, string serverUrl) { - var storageProvider = storageProviderFactory.GetProvider(picture.StorageType); - return new PictureResponse { Id = picture.Id, Name = picture.Name, - Path = storageProvider.GetUrl(picture.Path), - ThumbnailPath = storageProvider.GetUrl(picture.ThumbnailPath), + Path = storageService.GetUrl(picture.StorageType, picture.Path), + ThumbnailPath = storageService.GetUrl(picture.StorageType, picture.ThumbnailPath), Description = picture.Description, CreatedAt = picture.CreatedAt, Tags = picture.Tags != null ? picture.Tags.Select(t => t.Name).ToList() : new List(), @@ -452,11 +451,8 @@ public class PictureService( string fileExtension = Path.GetExtension(fileName); string newFileName = $"{Guid.NewGuid()}{fileExtension}"; - // 获取对应的存储提供者 - var storageProvider = storageProviderFactory.GetProvider(storageType.Value); - - // 使用存储提供者保存文件 - string relativePath = await storageProvider.SaveAsync(fileStream, fileName, contentType); + // 使用存储服务保存文件 + string relativePath = await storageService.SaveAsync(storageType.Value, fileStream, fileName, contentType); // 创建基本的Picture对象,使用文件名作为标题和描述 string initialTitle = Path.GetFileNameWithoutExtension(fileName); @@ -522,8 +518,8 @@ public class PictureService( { Id = picture.Id, Name = picture.Name, - Path = storageProvider.GetUrl(relativePath), - ThumbnailPath = isAnonymous ? storageProvider.GetUrl(relativePath) : null, + Path = storageService.GetUrl(picture.StorageType, relativePath), + ThumbnailPath = isAnonymous ? storageService.GetUrl(picture.StorageType, relativePath) : null, Description = picture.Description, CreatedAt = picture.CreatedAt, Tags = new List(), @@ -602,21 +598,13 @@ public class PictureService( try { - // 根据存储类型获取相应的存储提供者并删除文件 - var storageProvider = storageProviderFactory.GetProvider(storageType); - await storageProvider.DeleteAsync(path); + // 使用存储服务删除文件 + await storageService.DeleteAsync(storageType, path); // 删除缩略图 - if (storageType == StorageType.Local) + if (!string.IsNullOrEmpty(thumbnailPath)) { - // 对于本地存储,使用本地存储提供者删除缩略图 - await storageProvider.DeleteAsync(thumbnailPath); - } - else - { - // 对于其他存储类型(如Telegram),使用相同的存储提供者删除缩略图 - // 因为缩略图元数据格式与原文件相同 - await storageProvider.DeleteAsync(thumbnailPath); + await storageService.DeleteAsync(storageType, thumbnailPath); } } catch (Exception ex) @@ -697,8 +685,8 @@ public class PictureService( { Id = picture.Id, Name = picture.Name, - Path = storageProviderFactory.GetProvider(picture.StorageType).GetUrl(picture.Path), - ThumbnailPath = storageProviderFactory.GetProvider(picture.StorageType).GetUrl(picture.ThumbnailPath), + Path = storageService.GetUrl(picture.StorageType, picture.Path), + ThumbnailPath = storageService.GetUrl(picture.StorageType, picture.ThumbnailPath), Description = picture.Description, CreatedAt = picture.CreatedAt, Tags = picture.Tags?.Select(t => t.Name).ToList() ?? new List(), diff --git a/Services/StorageProvider/CosStorageProvider.cs b/Services/StorageProvider/CosStorageProvider.cs index 49883f9..fce1891 100644 --- a/Services/StorageProvider/CosStorageProvider.cs +++ b/Services/StorageProvider/CosStorageProvider.cs @@ -2,10 +2,10 @@ using Foxel.Services.Interface; using COSXML; using COSXML.Auth; using COSXML.Model.Object; -using COSXML.Model.Bucket; using COSXML.Transfer; using COSXML.CosException; using COSXML.Model.Tag; +using Foxel.Services.Attributes; namespace Foxel.Services.StorageProvider; @@ -20,7 +20,7 @@ public class CustomQCloudCredentialProvider : DefaultSessionQCloudCredentialProv Refresh(); } - public override void Refresh() + public sealed override void Refresh() { try { @@ -29,8 +29,8 @@ public class CustomQCloudCredentialProvider : DefaultSessionQCloudCredentialProv string tmpToken = _configService["Storage:CosStorageToken"]; long tmpStartTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); long tmpExpiredTime = tmpStartTime + 7200; - SetQCloudCredential(tmpSecretId, tmpSecretKey, - String.Format("{0};{1}", tmpStartTime, tmpExpiredTime), tmpToken); + SetQCloudCredential(tmpSecretId, tmpSecretKey, + $"{tmpStartTime};{tmpExpiredTime}", tmpToken); } catch (Exception ex) { @@ -39,11 +39,9 @@ public class CustomQCloudCredentialProvider : DefaultSessionQCloudCredentialProv } } } - +[StorageProvider(StorageType.Cos)] public class CosStorageProvider : IStorageProvider { - private readonly string _secretId; - private readonly string _secretKey; private readonly string _bucketName; private readonly string _region; private readonly string _cdnUrl; @@ -55,14 +53,12 @@ public class CosStorageProvider : IStorageProvider public CosStorageProvider(IConfigService configService) { _configService = configService; - _secretId = configService["Storage:CosStorageSecretId"]; - _secretKey = configService["Storage:CosStorageSecretKey"]; _bucketName = configService["Storage:CosStorageBucketName"]; _region = configService["Storage:CosStorageRegion"]; - _cdnUrl = configService["Storage:CosStorageCdnUrl"] ?? string.Empty; + _cdnUrl = configService["Storage:CosStorageCdnUrl"]; // 检查桶是否为公开读取(从配置获取) - bool.TryParse(configService["Storage:CosStoragePublicRead"] ?? "false", out _isPublicRead); + bool.TryParse(configService["Storage:CosStoragePublicRead"], out _isPublicRead); // 在构造函数中初始化客户端,作为单例使用 _cosXmlClient = CreateClient(); @@ -96,7 +92,7 @@ public class CosStorageProvider : IStorageProvider string tempPath = Path.GetTempFileName(); try { - using (var fileStream2 = new FileStream(tempPath, FileMode.Create)) + await using (var fileStream2 = new FileStream(tempPath, FileMode.Create)) { await fileStream.CopyToAsync(fileStream2); } @@ -105,7 +101,7 @@ public class CosStorageProvider : IStorageProvider var transferManager = new TransferManager(_cosXmlClient, transferConfig); var uploadTask = new COSXMLUploadTask(_bucketName, objectKey); uploadTask.SetSrcPath(tempPath); - var result = await transferManager.UploadAsync(uploadTask); + await transferManager.UploadAsync(uploadTask); return objectKey; } finally @@ -216,7 +212,7 @@ public class CosStorageProvider : IStorageProvider var transferConfig = new TransferConfig(); var transferManager = new TransferManager(_cosXmlClient, transferConfig); var downloadTask = new COSXMLDownloadTask(_bucketName, storagePath, tempDir, fileName); - var result = await transferManager.DownloadAsync(downloadTask); + await transferManager.DownloadAsync(downloadTask); return localFilePath; } catch (CosClientException clientEx) diff --git a/Services/StorageProvider/LocalStorageProvider.cs b/Services/StorageProvider/LocalStorageProvider.cs index 4d4852a..81dfdf6 100644 --- a/Services/StorageProvider/LocalStorageProvider.cs +++ b/Services/StorageProvider/LocalStorageProvider.cs @@ -1,7 +1,9 @@ +using Foxel.Services.Attributes; using Foxel.Services.Interface; namespace Foxel.Services.StorageProvider; +[StorageProvider(StorageType.Local)] public class LocalStorageProvider(IConfigService config) : IStorageProvider { private readonly string _baseDirectory = Path.Combine(Directory.GetCurrentDirectory(), "Uploads"); diff --git a/Services/StorageProvider/S3StorageProvider.cs b/Services/StorageProvider/S3StorageProvider.cs index 69368a9..dc06f94 100644 --- a/Services/StorageProvider/S3StorageProvider.cs +++ b/Services/StorageProvider/S3StorageProvider.cs @@ -1,3 +1,4 @@ +using Foxel.Services.Attributes; using Foxel.Services.Interface; using Amazon.S3; using Amazon.S3.Model; @@ -5,6 +6,7 @@ using Amazon.S3.Transfer; namespace Foxel.Services.StorageProvider; +[StorageProvider(StorageType.S3)] public class S3StorageProvider : IStorageProvider { private readonly string _accessKey; @@ -13,7 +15,6 @@ public class S3StorageProvider : IStorageProvider private readonly string _region; private readonly string _endpoint; private readonly bool _usePathStyleUrls; - private readonly string _serverUrl; private readonly string _cdnUrl; public S3StorageProvider(IConfigService configService) @@ -22,9 +23,8 @@ public class S3StorageProvider : IStorageProvider _secretKey = configService["Storage:S3StorageSecretKey"]; _bucketName = configService["Storage:S3StorageBucketName"]; _region = configService["Storage:S3StorageRegion"]; - _serverUrl = configService["AppSettings:ServerUrl"]; - _cdnUrl = configService["Storage:S3StorageCdnUrl"] ?? string.Empty; - _endpoint = configService["Storage:S3StorageEndpoint"] ?? $"https://s3.{_region}.amazonaws.com"; + _cdnUrl = configService["Storage:S3StorageCdnUrl"]; + _endpoint = configService["Storage:S3StorageEndpoint"]; _usePathStyleUrls = bool.TryParse(configService["Storage:S3StorageUsePathStyleUrls"], out var usePathStyle) && usePathStyle; } @@ -163,7 +163,7 @@ public class S3StorageProvider : IStorageProvider }; using var response = await client.GetObjectAsync(request); - using var fileStream = new FileStream(tempFilePath, FileMode.Create); + await using var fileStream = new FileStream(tempFilePath, FileMode.Create); await response.ResponseStream.CopyToAsync(fileStream); return tempFilePath; diff --git a/Services/StorageProvider/TelegramStorageProvider.cs b/Services/StorageProvider/TelegramStorageProvider.cs index 638f8a7..c18c3bf 100644 --- a/Services/StorageProvider/TelegramStorageProvider.cs +++ b/Services/StorageProvider/TelegramStorageProvider.cs @@ -1,3 +1,4 @@ +using Foxel.Services.Attributes; using Foxel.Services.Interface; using System.Net.Http.Headers; using System.Text.Json; @@ -5,6 +6,7 @@ using System.Text.Json.Serialization; namespace Foxel.Services.StorageProvider; +[StorageProvider(StorageType.Telegram)] public class TelegramStorageProvider(IConfigService configService) : IStorageProvider { private readonly string _botToken = configService["Storage:TelegramStorageBotToken"]; @@ -83,7 +85,7 @@ public class TelegramStorageProvider(IConfigService configService) : IStoragePro using var httpClient = new HttpClient(); var url = $"https://api.telegram.org/bot{_botToken}/deleteMessage?chat_id={metadata.ChatId}&message_id={metadata.MessageId}"; - var response = await httpClient.GetAsync(url); + await httpClient.GetAsync(url); } catch (Exception ex) { @@ -165,8 +167,8 @@ public class TelegramStorageProvider(IConfigService configService) : IStoragePro string tempFilePath = Path.Combine(tempDir, tempFileName); // 保存文件 - using var fileStream = await fileResponse.Content.ReadAsStreamAsync(); - using var outputStream = new FileStream(tempFilePath, FileMode.Create); + await using var fileStream = await fileResponse.Content.ReadAsStreamAsync(); + await using var outputStream = new FileStream(tempFilePath, FileMode.Create); await fileStream.CopyToAsync(outputStream); return tempFilePath; diff --git a/Services/StorageProviderFactory.cs b/Services/StorageProviderFactory.cs deleted file mode 100644 index 32cc90e..0000000 --- a/Services/StorageProviderFactory.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Foxel.Models.DataBase; -using Foxel.Services.Interface; -using Foxel.Services.StorageProvider; -using Pgvector.EntityFrameworkCore; - -namespace Foxel.Services; - -public class StorageProviderFactory( - LocalStorageProvider localStorageProvider, - TelegramStorageProvider telegramStorageProvider, - CosStorageProvider cosStorageProvider, - S3StorageProvider s3StorageProvider) : IStorageProviderFactory -{ - public IStorageProvider GetProvider(StorageType storageType) - { - return storageType switch - { - StorageType.Local => localStorageProvider, - StorageType.Telegram => telegramStorageProvider, - StorageType.S3 => s3StorageProvider, - StorageType.Cos => cosStorageProvider, - _ => throw new ArgumentException($"不支持的存储类型: {storageType}") - }; - } -} diff --git a/Services/StorageService.cs b/Services/StorageService.cs new file mode 100644 index 0000000..2a7a127 --- /dev/null +++ b/Services/StorageService.cs @@ -0,0 +1,106 @@ +using System.Reflection; +using Foxel.Models.DataBase; +using Foxel.Services.Attributes; +using Foxel.Services.Interface; + +namespace Foxel.Services; + +/// +/// 统一的存储服务实现 +/// +public class StorageService : IStorageService +{ + private readonly IServiceProvider _serviceProvider; + private readonly Dictionary _storageProviders = new(); + + public StorageService(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + RegisterStorageProviders(); + } + + /// + /// 使用反射扫描和注册所有标记了StorageProviderAttribute的存储提供者 + /// + private void RegisterStorageProviders() + { + // 获取当前应用程序域中所有程序集 + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + + foreach (var assembly in assemblies) + { + try + { + // 扫描每个程序集中的所有类型 + var types = assembly.GetTypes() + .Where(type => type is { IsClass: true, IsAbstract: false } && + type.GetInterfaces().Contains(typeof(IStorageProvider)) && + type.GetCustomAttribute() != null); + + foreach (var type in types) + { + var attribute = type.GetCustomAttribute(); + if (attribute != null) + { + // 注册存储提供者类型与对应的存储类型 + _storageProviders[attribute.StorageType] = type; + } + } + } + catch (Exception ex) + { + Console.WriteLine($"扫描程序集 {assembly.FullName} 出错: {ex.Message}"); + // 继续扫描其他程序集 + } + } + } + + /// + /// 获取指定存储类型的提供者实例 + /// + public IStorageProvider GetProvider(StorageType storageType) + { + if (!_storageProviders.TryGetValue(storageType, out var providerType)) + { + throw new ArgumentException($"未找到存储类型 {storageType} 的提供者"); + } + + return (IStorageProvider)_serviceProvider.GetRequiredService(providerType); + } + + /// + /// 使用指定存储类型保存文件 + /// + public Task SaveAsync(StorageType storageType, Stream fileStream, string fileName, string contentType) + { + var provider = GetProvider(storageType); + return provider.SaveAsync(fileStream, fileName, contentType); + } + + /// + /// 使用指定存储类型删除文件 + /// + public Task DeleteAsync(StorageType storageType, string storagePath) + { + var provider = GetProvider(storageType); + return provider.DeleteAsync(storagePath); + } + + /// + /// 使用指定存储类型获取文件URL + /// + public string GetUrl(StorageType storageType, string storagePath) + { + var provider = GetProvider(storageType); + return provider.GetUrl(storagePath); + } + + /// + /// 使用指定存储类型下载文件 + /// + public Task DownloadFileAsync(StorageType storageType, string storagePath) + { + var provider = GetProvider(storageType); + return provider.DownloadFileAsync(storagePath); + } +}