Files
Foxel/Services/Storage/Providers/S3StorageProvider.cs

201 lines
6.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Amazon.S3;
using Amazon.S3.Model;
using Amazon.S3.Transfer;
using Foxel.Services.Attributes;
using Foxel.Services.Configuration;
using Microsoft.Extensions.Logging;
namespace Foxel.Services.Storage.Providers;
public class S3StorageConfig
{
public string AccessKey { get; set; } = string.Empty;
public string SecretKey { get; set; } = string.Empty;
public string Endpoint { get; set; } = string.Empty;
public string? Region { get; set; } // Region 可能为空特别是对于非AWS S3兼容存储
public bool UsePathStyleUrls { get; set; } = false;
public string BucketName { get; set; } = string.Empty;
public string? CdnUrl { get; set; }
}
[StorageProvider(StorageType.S3)]
public class S3StorageProvider : IStorageProvider
{
private readonly S3StorageConfig _s3Config;
private readonly IConfigService _configService; // 保留用于可能的应用级配置
private readonly ILogger<S3StorageProvider> _logger;
public S3StorageProvider(S3StorageConfig s3Config, IConfigService configService, ILogger<S3StorageProvider> logger)
{
_s3Config = s3Config;
_configService = configService;
_logger = logger;
if (string.IsNullOrEmpty(_s3Config.AccessKey) ||
string.IsNullOrEmpty(_s3Config.SecretKey) ||
string.IsNullOrEmpty(_s3Config.Endpoint) ||
string.IsNullOrEmpty(_s3Config.BucketName))
{
_logger.LogError("S3 Storage配置不完整 (AccessKey, SecretKey, Endpoint, BucketName 都是必需的).");
throw new InvalidOperationException("S3 Storage配置不完整。");
}
}
private AmazonS3Client CreateClient()
{
string accessKey = _s3Config.AccessKey;
string secretKey = _s3Config.SecretKey;
string endpoint = _s3Config.Endpoint;
string? region = _s3Config.Region;
bool usePathStyleUrls = _s3Config.UsePathStyleUrls;
var config = new AmazonS3Config
{
ServiceURL = endpoint,
UseHttp = !endpoint.StartsWith("https", StringComparison.OrdinalIgnoreCase),
ForcePathStyle = usePathStyleUrls
};
if (!string.IsNullOrEmpty(region) && endpoint.Contains("amazonaws.com", StringComparison.OrdinalIgnoreCase))
{
config.RegionEndpoint = Amazon.RegionEndpoint.GetBySystemName(region);
}
return new AmazonS3Client(
accessKey,
secretKey,
config
);
}
public async Task<string> SaveAsync(Stream fileStream, string fileName, string contentType)
{
try
{
// 创建唯一的文件存储路径
string currentDate = DateTime.Now.ToString("yyyy/MM");
// fileName 参数现在是期望的最终文件名部分,例如 "guid.ext"
// string ext = Path.GetExtension(fileName); // 旧的 fileName 是原始文件名,现在 fileName 是目标文件名
// string objectKey = $"{currentDate}/{Guid.NewGuid()}{ext}"; // 旧逻辑
string objectKey = $"{currentDate}/{fileName}"; // 新逻辑
using var client = CreateClient();
using var transferUtility = new TransferUtility(client);
var uploadRequest = new TransferUtilityUploadRequest
{
InputStream = fileStream,
Key = objectKey,
BucketName = _s3Config.BucketName,
ContentType = contentType
};
await transferUtility.UploadAsync(uploadRequest);
// 返回文件的路径
return objectKey;
}
catch (Exception ex)
{
_logger.LogError(ex, "上传文件到S3时出错");
throw;
}
}
public async Task DeleteAsync(string storagePath)
{
try
{
if (string.IsNullOrEmpty(storagePath))
return;
using var client = CreateClient();
var deleteRequest = new DeleteObjectRequest
{
BucketName = _s3Config.BucketName,
Key = storagePath
};
await client.DeleteObjectAsync(deleteRequest);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "从S3删除文件时出错");
}
}
public string GetUrl(int pictureId,string storagePath)
{
try
{
if (string.IsNullOrEmpty(storagePath))
return "/images/unavailable.gif";
string? cdnUrl = _s3Config.CdnUrl;
// 如果配置了CDN URL使用CDN
if (!string.IsNullOrEmpty(cdnUrl))
{
return $"{cdnUrl.TrimEnd('/')}/{storagePath}";
}
// 否则使用S3直链或生成预签名URL
using var client = CreateClient();
var request = new GetPreSignedUrlRequest
{
BucketName = _s3Config.BucketName,
Key = storagePath,
Expires = DateTime.UtcNow.AddHours(1) // URL有效期1小时
};
return client.GetPreSignedURL(request);
}
catch (Exception ex)
{
_logger.LogError(ex, "生成S3文件URL时出错");
return "/images/unavailable.gif";
}
}
public async Task<string> DownloadFileAsync(string storagePath)
{
try
{
if (string.IsNullOrEmpty(storagePath))
{
throw new ArgumentException("存储路径不能为空");
}
// 创建临时目录
var tempDir = Path.Combine(Path.GetTempPath(), "FoxelS3Temp");
if (!Directory.Exists(tempDir))
{
Directory.CreateDirectory(tempDir);
}
// 创建临时文件名
string fileName = Path.GetFileName(storagePath);
string tempFilePath = Path.Combine(tempDir, fileName);
// 下载文件
using var client = CreateClient();
var request = new GetObjectRequest
{
BucketName = _s3Config.BucketName,
Key = storagePath
};
using var response = await client.GetObjectAsync(request);
await using var fileStream = new FileStream(tempFilePath, FileMode.Create);
await response.ResponseStream.CopyToAsync(fileStream);
return tempFilePath;
}
catch (Exception ex)
{
_logger.LogError(ex, "从S3下载文件时出错");
throw;
}
}
}