using Amazon.S3; using Amazon.S3.Model; using Amazon.S3.Transfer; using Foxel.Services.Attributes; using Foxel.Services.Configuration; namespace Foxel.Services.Storage.Providers; [StorageProvider(StorageType.S3)] public class S3StorageProvider(IConfigService configService) : IStorageProvider { private AmazonS3Client CreateClient() { string accessKey = configService["Storage:S3StorageAccessKey"]; string secretKey = configService["Storage:S3StorageSecretKey"]; string endpoint = configService["Storage:S3StorageEndpoint"]; string region = configService["Storage:S3StorageRegion"]; bool usePathStyleUrls = bool.TryParse(configService["Storage:S3StorageUsePathStyleUrls"], out var usePathStyle) && usePathStyle; var config = new AmazonS3Config { ServiceURL = endpoint, UseHttp = !endpoint.StartsWith("https", StringComparison.OrdinalIgnoreCase), ForcePathStyle = usePathStyleUrls }; if (!string.IsNullOrEmpty(region) && endpoint.Contains("amazonaws.com")) { config.RegionEndpoint = Amazon.RegionEndpoint.GetBySystemName(region); } return new AmazonS3Client( accessKey, secretKey, config ); } public async Task SaveAsync(Stream fileStream, string fileName, string contentType) { try { // 创建唯一的文件存储路径 string currentDate = DateTime.Now.ToString("yyyy/MM"); string ext = Path.GetExtension(fileName); string objectKey = $"{currentDate}/{Guid.NewGuid()}{ext}"; using var client = CreateClient(); using var transferUtility = new TransferUtility(client); var uploadRequest = new TransferUtilityUploadRequest { InputStream = fileStream, Key = objectKey, BucketName = configService["Storage:S3StorageBucketName"], ContentType = contentType }; await transferUtility.UploadAsync(uploadRequest); // 返回文件的路径 return objectKey; } catch (Exception ex) { Console.WriteLine($"上传文件到S3时出错: {ex.Message}"); throw; } } public async Task DeleteAsync(string storagePath) { try { if (string.IsNullOrEmpty(storagePath)) return; using var client = CreateClient(); var deleteRequest = new DeleteObjectRequest { BucketName = configService["Storage:S3StorageBucketName"], Key = storagePath }; await client.DeleteObjectAsync(deleteRequest); } catch (Exception ex) { Console.WriteLine($"从S3删除文件时出错: {ex.Message}"); } } public string GetUrl(string storagePath) { try { if (string.IsNullOrEmpty(storagePath)) return "/images/unavailable.gif"; string cdnUrl = configService["Storage:S3StorageCdnUrl"]; // 如果配置了CDN URL,使用CDN if (!string.IsNullOrEmpty(cdnUrl)) { return $"{cdnUrl}/{storagePath}"; } // 否则使用S3直链或生成预签名URL using var client = CreateClient(); var request = new GetPreSignedUrlRequest { BucketName = configService["Storage:S3StorageBucketName"], Key = storagePath, Expires = DateTime.UtcNow.AddHours(1) // URL有效期1小时 }; return client.GetPreSignedURL(request); } catch (Exception ex) { Console.WriteLine($"生成S3文件URL时出错: {ex.Message}"); return "/images/unavailable.gif"; } } public async Task 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 = configService["Storage:S3StorageBucketName"], 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) { Console.WriteLine($"从S3下载文件时出错: {ex.Message}"); throw; } } }