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

262 lines
9.2 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 COSXML;
using COSXML.Auth;
using COSXML.CosException;
using COSXML.Model.Object;
using COSXML.Model.Tag;
using COSXML.Transfer;
using Foxel.Services.Attributes;
using Foxel.Services.Configuration;
using Microsoft.Extensions.Logging;
namespace Foxel.Services.Storage.Providers;
public class CosStorageConfig
{
public string Region { get; set; } = string.Empty;
public string SecretId { get; set; } = string.Empty;
public string SecretKey { get; set; } = string.Empty;
public string? Token { get; set; } // Token 可能为空
public string BucketName { get; set; } = string.Empty;
public string? CdnUrl { get; set; }
public bool PublicRead { get; set; } = false;
}
public class CustomQCloudCredentialProvider : DefaultSessionQCloudCredentialProvider
{
private readonly CosStorageConfig _config;
private readonly ILogger<CustomQCloudCredentialProvider> _logger;
public CustomQCloudCredentialProvider(CosStorageConfig config, ILogger<CustomQCloudCredentialProvider> logger)
: base(null, null, 0L, null) // Base constructor parameters are set in Refresh
{
_config = config;
_logger = logger;
Refresh();
}
public sealed override void Refresh()
{
try
{
string tmpSecretId = _config.SecretId;
string tmpSecretKey = _config.SecretKey;
string? tmpToken = _config.Token;
long tmpStartTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
// 腾讯云建议临时密钥有效期最长2小时7200秒
long tmpExpiredTime = tmpStartTime + 7200;
SetQCloudCredential(tmpSecretId, tmpSecretKey,
$"{tmpStartTime};{tmpExpiredTime}", tmpToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "刷新临时密钥时出错");
throw;
}
}
}
[StorageProvider(StorageType.Cos)]
public class CosStorageProvider : IStorageProvider
{
private readonly CosStorageConfig _cosConfig;
private readonly IConfigService _configService; // 保留用于可能的应用级配置
private readonly ILogger<CosStorageProvider> _logger;
public CosStorageProvider(CosStorageConfig cosConfig, IConfigService configService, ILogger<CosStorageProvider> logger)
{
_cosConfig = cosConfig;
_configService = configService; // 存储起来以备后用
_logger = logger;
if (string.IsNullOrEmpty(_cosConfig.Region) ||
string.IsNullOrEmpty(_cosConfig.SecretId) ||
string.IsNullOrEmpty(_cosConfig.SecretKey) ||
string.IsNullOrEmpty(_cosConfig.BucketName))
{
_logger.LogError("COS Storage配置不完整 (Region, SecretId, SecretKey, BucketName 都是必需的).");
throw new InvalidOperationException("COS Storage配置不完整。");
}
}
private CosXml CreateClient()
{
var config = new CosXmlConfig.Builder()
.IsHttps(true)
.SetRegion(_cosConfig.Region)
.SetDebugLog(true)
.Build();
var cosCredentialProvider = new CustomQCloudCredentialProvider(_cosConfig,
_logger.IsEnabled(LogLevel.Debug) ?
Microsoft.Extensions.Logging.LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger<CustomQCloudCredentialProvider>() :
Microsoft.Extensions.Logging.Abstractions.NullLogger<CustomQCloudCredentialProvider>.Instance);
return new CosXmlServer(config, cosCredentialProvider);
}
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}"; // 新逻辑
// 创建临时文件
string tempPath = Path.GetTempFileName();
try
{
await using (var fileStream2 = new FileStream(tempPath, FileMode.Create))
{
await fileStream.CopyToAsync(fileStream2);
}
var cosXmlClient = CreateClient();
var transferConfig = new TransferConfig();
var transferManager = new TransferManager(cosXmlClient, transferConfig);
var uploadTask = new COSXMLUploadTask(_cosConfig.BucketName, objectKey);
uploadTask.SetSrcPath(tempPath);
await transferManager.UploadAsync(uploadTask);
return objectKey;
}
finally
{
// 确保临时文件被删除
if (File.Exists(tempPath))
{
File.Delete(tempPath);
}
}
}
catch (CosClientException clientEx)
{
_logger.LogError(clientEx, "COS客户端异常");
throw;
}
catch (CosServerException serverEx)
{
_logger.LogError(serverEx, "COS服务器异常: {ServerInfo}", serverEx.GetInfo());
throw;
}
catch (Exception ex)
{
_logger.LogError(ex, "上传文件到腾讯云COS时出错");
throw;
}
}
public async Task DeleteAsync(string storagePath)
{
try
{
if (string.IsNullOrEmpty(storagePath))
return;
var cosXmlClient = CreateClient();
var request = new DeleteObjectRequest(_cosConfig.BucketName, storagePath);
await Task.Run(() => cosXmlClient.DeleteObject(request));
}
catch (CosClientException clientEx)
{
_logger.LogWarning(clientEx, "COS客户端异常");
}
catch (CosServerException serverEx)
{
_logger.LogWarning(serverEx, "COS服务器异常: {ServerInfo}", serverEx.GetInfo());
}
catch (Exception ex)
{
_logger.LogWarning(ex, "从腾讯云COS删除文件时出错");
}
}
public string GetUrl(int pictureId,string storagePath)
{
try
{
if (string.IsNullOrEmpty(storagePath))
return "/images/unavailable.gif";
string? cdnUrl = _cosConfig.CdnUrl;
string bucketName = _cosConfig.BucketName;
string region = _cosConfig.Region;
bool isPublicRead = _cosConfig.PublicRead;
// 优先使用CDN
if (!string.IsNullOrEmpty(cdnUrl))
return $"{cdnUrl.TrimEnd('/')}/{storagePath}";
// 公开读取的桶可直接访问
if (isPublicRead)
return $"https://{bucketName}.cos.{region}.myqcloud.com/{storagePath}";
var cosXmlClient = CreateClient();
var bucketParts = bucketName.Split('-');
var request = new PreSignatureStruct
{
bucket = bucketParts[0],
appid = bucketParts[1],
region = region,
key = storagePath,
httpMethod = "GET",
isHttps = true,
signDurationSecond = 3600 * 24
};
var url = cosXmlClient.GenerateSignURL(request);
return url;
}
catch (Exception ex)
{
_logger.LogError(ex, "生成腾讯云COS文件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(), "FoxelCosTemp");
if (!Directory.Exists(tempDir))
{
Directory.CreateDirectory(tempDir);
}
string bucketName = _cosConfig.BucketName;
string fileName = Path.GetFileName(storagePath);
var cosXmlClient = CreateClient();
var transferConfig = new TransferConfig();
var transferManager = new TransferManager(cosXmlClient, transferConfig);
var downloadTask = new COSXMLDownloadTask(bucketName, storagePath, tempDir, fileName);
await transferManager.DownloadAsync(downloadTask);
return Path.Combine(tempDir, fileName);
}
catch (CosClientException clientEx)
{
_logger.LogError(clientEx, "COS客户端异常");
throw;
}
catch (CosServerException serverEx)
{
_logger.LogError(serverEx, "COS服务器异常: {ServerInfo}", serverEx.GetInfo());
throw;
}
catch (Exception ex)
{
_logger.LogError(ex, "从腾讯云COS下载文件时出错");
throw;
}
}
}