mirror of
https://github.com/DrizzleTime/Foxel.git
synced 2026-05-16 19:57:36 +08:00
feat(storage): refactor storage service and provider interface, support dynamic registration of storage providers
This commit is contained in:
33
Services/Attributes/StorageProviderAttribute.cs
Normal file
33
Services/Attributes/StorageProviderAttribute.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using Foxel.Models.DataBase;
|
||||
|
||||
namespace Foxel.Services.Attributes;
|
||||
|
||||
public enum StorageType
|
||||
{
|
||||
Local = 0,
|
||||
Telegram = 1,
|
||||
S3 = 2,
|
||||
Cos = 3,
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 标记存储提供者类的特性
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class StorageProviderAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 存储类型
|
||||
/// </summary>
|
||||
public StorageType StorageType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="storageType">存储类型</param>
|
||||
public StorageProviderAttribute(StorageType storageType)
|
||||
{
|
||||
StorageType = storageType;
|
||||
}
|
||||
}
|
||||
@@ -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<IPictureService>();
|
||||
var aiService = scope.ServiceProvider.GetRequiredService<IAiService>();
|
||||
var storageProviderFactory = scope.ServiceProvider.GetRequiredService<IStorageProviderFactory>();
|
||||
var storageService = scope.ServiceProvider.GetRequiredService<IStorageService>();
|
||||
|
||||
// 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);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Foxel.Models;
|
||||
using Foxel.Models.DataBase;
|
||||
using Foxel.Models.Response.Picture;
|
||||
using Foxel.Services.Attributes;
|
||||
|
||||
namespace Foxel.Services.Interface;
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
using Foxel.Models.DataBase;
|
||||
using Foxel.Services.Interface;
|
||||
|
||||
namespace Foxel.Services.Interface;
|
||||
|
||||
public interface IStorageProviderFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据存储类型获取对应的存储提供者
|
||||
/// </summary>
|
||||
/// <param name="storageType">存储类型</param>
|
||||
/// <returns>存储提供者实例</returns>
|
||||
IStorageProvider GetProvider(StorageType storageType);
|
||||
}
|
||||
50
Services/Interface/IStorageService.cs
Normal file
50
Services/Interface/IStorageService.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using Foxel.Models.DataBase;
|
||||
using Foxel.Services.Attributes;
|
||||
|
||||
namespace Foxel.Services.Interface;
|
||||
|
||||
/// <summary>
|
||||
/// 统一的存储服务接口
|
||||
/// </summary>
|
||||
public interface IStorageService
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据存储类型获取对应的存储提供者
|
||||
/// </summary>
|
||||
/// <param name="storageType">存储类型</param>
|
||||
/// <returns>存储提供者实例</returns>
|
||||
IStorageProvider GetProvider(StorageType storageType);
|
||||
|
||||
/// <summary>
|
||||
/// 使用指定存储类型保存文件
|
||||
/// </summary>
|
||||
/// <param name="storageType">存储类型</param>
|
||||
/// <param name="fileStream">文件流</param>
|
||||
/// <param name="fileName">文件名</param>
|
||||
/// <param name="contentType">内容类型</param>
|
||||
/// <returns>存储路径</returns>
|
||||
Task<string> SaveAsync(StorageType storageType, Stream fileStream, string fileName, string contentType);
|
||||
|
||||
/// <summary>
|
||||
/// 使用指定存储类型删除文件
|
||||
/// </summary>
|
||||
/// <param name="storageType">存储类型</param>
|
||||
/// <param name="storagePath">存储路径</param>
|
||||
Task DeleteAsync(StorageType storageType, string storagePath);
|
||||
|
||||
/// <summary>
|
||||
/// 使用指定存储类型获取文件URL
|
||||
/// </summary>
|
||||
/// <param name="storageType">存储类型</param>
|
||||
/// <param name="storagePath">存储路径</param>
|
||||
/// <returns>文件URL</returns>
|
||||
string GetUrl(StorageType storageType, string storagePath);
|
||||
|
||||
/// <summary>
|
||||
/// 使用指定存储类型下载文件
|
||||
/// </summary>
|
||||
/// <param name="storageType">存储类型</param>
|
||||
/// <param name="storagePath">存储路径</param>
|
||||
/// <returns>本地文件路径</returns>
|
||||
Task<string> DownloadFileAsync(StorageType storageType, string storagePath);
|
||||
}
|
||||
@@ -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<string>(),
|
||||
@@ -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<string>(),
|
||||
@@ -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<string>(),
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}")
|
||||
};
|
||||
}
|
||||
}
|
||||
106
Services/StorageService.cs
Normal file
106
Services/StorageService.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using System.Reflection;
|
||||
using Foxel.Models.DataBase;
|
||||
using Foxel.Services.Attributes;
|
||||
using Foxel.Services.Interface;
|
||||
|
||||
namespace Foxel.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 统一的存储服务实现
|
||||
/// </summary>
|
||||
public class StorageService : IStorageService
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly Dictionary<StorageType, Type> _storageProviders = new();
|
||||
|
||||
public StorageService(IServiceProvider serviceProvider)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
RegisterStorageProviders();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用反射扫描和注册所有标记了StorageProviderAttribute的存储提供者
|
||||
/// </summary>
|
||||
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<StorageProviderAttribute>() != null);
|
||||
|
||||
foreach (var type in types)
|
||||
{
|
||||
var attribute = type.GetCustomAttribute<StorageProviderAttribute>();
|
||||
if (attribute != null)
|
||||
{
|
||||
// 注册存储提供者类型与对应的存储类型
|
||||
_storageProviders[attribute.StorageType] = type;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"扫描程序集 {assembly.FullName} 出错: {ex.Message}");
|
||||
// 继续扫描其他程序集
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定存储类型的提供者实例
|
||||
/// </summary>
|
||||
public IStorageProvider GetProvider(StorageType storageType)
|
||||
{
|
||||
if (!_storageProviders.TryGetValue(storageType, out var providerType))
|
||||
{
|
||||
throw new ArgumentException($"未找到存储类型 {storageType} 的提供者");
|
||||
}
|
||||
|
||||
return (IStorageProvider)_serviceProvider.GetRequiredService(providerType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用指定存储类型保存文件
|
||||
/// </summary>
|
||||
public Task<string> SaveAsync(StorageType storageType, Stream fileStream, string fileName, string contentType)
|
||||
{
|
||||
var provider = GetProvider(storageType);
|
||||
return provider.SaveAsync(fileStream, fileName, contentType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用指定存储类型删除文件
|
||||
/// </summary>
|
||||
public Task DeleteAsync(StorageType storageType, string storagePath)
|
||||
{
|
||||
var provider = GetProvider(storageType);
|
||||
return provider.DeleteAsync(storagePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用指定存储类型获取文件URL
|
||||
/// </summary>
|
||||
public string GetUrl(StorageType storageType, string storagePath)
|
||||
{
|
||||
var provider = GetProvider(storageType);
|
||||
return provider.GetUrl(storagePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用指定存储类型下载文件
|
||||
/// </summary>
|
||||
public Task<string> DownloadFileAsync(StorageType storageType, string storagePath)
|
||||
{
|
||||
var provider = GetProvider(storageType);
|
||||
return provider.DownloadFileAsync(storagePath);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user