mirror of
https://github.com/DrizzleTime/Foxel.git
synced 2026-09-05 23:57:19 +08:00
refactor(storage): update storage providers to use IConfigService for configuration management
This commit is contained in:
@@ -14,9 +14,17 @@ public class AiService : IAiService
|
|||||||
{
|
{
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
_configService = configService;
|
_configService = configService;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConfigureHttpClient()
|
||||||
|
{
|
||||||
string apiKey = _configService["AI:ApiKey"];
|
string apiKey = _configService["AI:ApiKey"];
|
||||||
string baseUrl = _configService["AI:ApiEndpoint"];
|
string baseUrl = _configService["AI:ApiEndpoint"];
|
||||||
_httpClient.BaseAddress = new Uri(baseUrl);
|
|
||||||
|
if (_httpClient.BaseAddress?.ToString() != baseUrl)
|
||||||
|
{
|
||||||
|
_httpClient.BaseAddress = new Uri(baseUrl);
|
||||||
|
}
|
||||||
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
|
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,6 +32,7 @@ public class AiService : IAiService
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
ConfigureHttpClient();
|
||||||
string model = _configService["AI:Model"];
|
string model = _configService["AI:Model"];
|
||||||
var imageUrl = new ImageUrl
|
var imageUrl = new ImageUrl
|
||||||
{
|
{
|
||||||
@@ -83,6 +92,9 @@ public class AiService : IAiService
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// 确保使用最新配置
|
||||||
|
ConfigureHttpClient();
|
||||||
|
|
||||||
if (availableTags.Count == 0)
|
if (availableTags.Count == 0)
|
||||||
return new List<string>();
|
return new List<string>();
|
||||||
|
|
||||||
@@ -189,6 +201,8 @@ public class AiService : IAiService
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// 确保使用最新配置
|
||||||
|
ConfigureHttpClient();
|
||||||
|
|
||||||
string model = _configService["AI:Model"];
|
string model = _configService["AI:Model"];
|
||||||
|
|
||||||
@@ -329,6 +343,8 @@ public class AiService : IAiService
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// 确保使用最新配置
|
||||||
|
ConfigureHttpClient();
|
||||||
|
|
||||||
string model = _configService["AI:EmbeddingModel"];
|
string model = _configService["AI:EmbeddingModel"];
|
||||||
|
|
||||||
|
|||||||
@@ -39,42 +39,19 @@ public class CustomQCloudCredentialProvider : DefaultSessionQCloudCredentialProv
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[StorageProvider(StorageType.Cos)]
|
[StorageProvider(StorageType.Cos)]
|
||||||
public class CosStorageProvider : IStorageProvider
|
public class CosStorageProvider(IConfigService configService) : IStorageProvider
|
||||||
{
|
{
|
||||||
private readonly string _bucketName;
|
|
||||||
private readonly string _region;
|
|
||||||
private readonly string _cdnUrl;
|
|
||||||
private readonly IConfigService _configService;
|
|
||||||
private readonly CosXml _cosXmlClient;
|
|
||||||
|
|
||||||
private readonly bool _isPublicRead;
|
|
||||||
|
|
||||||
public CosStorageProvider(IConfigService configService)
|
|
||||||
{
|
|
||||||
_configService = configService;
|
|
||||||
_bucketName = configService["Storage:CosStorageBucketName"];
|
|
||||||
_region = configService["Storage:CosStorageRegion"];
|
|
||||||
_cdnUrl = configService["Storage:CosStorageCdnUrl"];
|
|
||||||
|
|
||||||
// 检查桶是否为公开读取(从配置获取)
|
|
||||||
bool.TryParse(configService["Storage:CosStoragePublicRead"], out _isPublicRead);
|
|
||||||
|
|
||||||
// 在构造函数中初始化客户端,作为单例使用
|
|
||||||
_cosXmlClient = CreateClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
private CosXml CreateClient()
|
private CosXml CreateClient()
|
||||||
{
|
{
|
||||||
// 优化配置:启用HTTPS和日志
|
|
||||||
var config = new CosXmlConfig.Builder()
|
var config = new CosXmlConfig.Builder()
|
||||||
.IsHttps(true) // 设置默认HTTPS请求
|
.IsHttps(true)
|
||||||
.SetRegion(_region)
|
.SetRegion(configService["Storage:CosStorageRegion"])
|
||||||
.SetDebugLog(true) // 显示日志
|
.SetDebugLog(true)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// 使用自定义凭证提供者,支持持续更新临时密钥
|
var cosCredentialProvider = new CustomQCloudCredentialProvider(configService);
|
||||||
var cosCredentialProvider = new CustomQCloudCredentialProvider(_configService);
|
|
||||||
|
|
||||||
return new CosXmlServer(config, cosCredentialProvider);
|
return new CosXmlServer(config, cosCredentialProvider);
|
||||||
}
|
}
|
||||||
@@ -97,9 +74,10 @@ public class CosStorageProvider : IStorageProvider
|
|||||||
await fileStream.CopyToAsync(fileStream2);
|
await fileStream.CopyToAsync(fileStream2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var cosXmlClient = CreateClient();
|
||||||
var transferConfig = new TransferConfig();
|
var transferConfig = new TransferConfig();
|
||||||
var transferManager = new TransferManager(_cosXmlClient, transferConfig);
|
var transferManager = new TransferManager(cosXmlClient, transferConfig);
|
||||||
var uploadTask = new COSXMLUploadTask(_bucketName, objectKey);
|
var uploadTask = new COSXMLUploadTask(configService["Storage:CosStorageBucketName"], objectKey);
|
||||||
uploadTask.SetSrcPath(tempPath);
|
uploadTask.SetSrcPath(tempPath);
|
||||||
await transferManager.UploadAsync(uploadTask);
|
await transferManager.UploadAsync(uploadTask);
|
||||||
return objectKey;
|
return objectKey;
|
||||||
@@ -137,8 +115,9 @@ public class CosStorageProvider : IStorageProvider
|
|||||||
if (string.IsNullOrEmpty(storagePath))
|
if (string.IsNullOrEmpty(storagePath))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var request = new DeleteObjectRequest(_bucketName, storagePath);
|
var cosXmlClient = CreateClient();
|
||||||
await Task.Run(() => _cosXmlClient.DeleteObject(request));
|
var request = new DeleteObjectRequest(configService["Storage:CosStorageBucketName"], storagePath);
|
||||||
|
await Task.Run(() => cosXmlClient.DeleteObject(request));
|
||||||
}
|
}
|
||||||
catch (CosClientException clientEx)
|
catch (CosClientException clientEx)
|
||||||
{
|
{
|
||||||
@@ -161,27 +140,33 @@ public class CosStorageProvider : IStorageProvider
|
|||||||
if (string.IsNullOrEmpty(storagePath))
|
if (string.IsNullOrEmpty(storagePath))
|
||||||
return "/images/unavailable.gif";
|
return "/images/unavailable.gif";
|
||||||
|
|
||||||
|
string cdnUrl = configService["Storage:CosStorageCdnUrl"];
|
||||||
|
string bucketName = configService["Storage:CosStorageBucketName"];
|
||||||
|
string region = configService["Storage:CosStorageRegion"];
|
||||||
|
bool isPublicRead = bool.TryParse(configService["Storage:CosStoragePublicRead"], out var publicRead) && publicRead;
|
||||||
|
|
||||||
// 优先使用CDN
|
// 优先使用CDN
|
||||||
if (!string.IsNullOrEmpty(_cdnUrl))
|
if (!string.IsNullOrEmpty(cdnUrl))
|
||||||
return $"{_cdnUrl}/{storagePath}";
|
return $"{cdnUrl}/{storagePath}";
|
||||||
|
|
||||||
// 公开读取的桶可直接访问
|
// 公开读取的桶可直接访问
|
||||||
if (_isPublicRead)
|
if (isPublicRead)
|
||||||
return $"https://{_bucketName}.cos.{_region}.myqcloud.com/{storagePath}";
|
return $"https://{bucketName}.cos.{region}.myqcloud.com/{storagePath}";
|
||||||
|
|
||||||
var bucketParts = _bucketName.Split('-');
|
var cosXmlClient = CreateClient();
|
||||||
|
var bucketParts = bucketName.Split('-');
|
||||||
var request = new PreSignatureStruct
|
var request = new PreSignatureStruct
|
||||||
{
|
{
|
||||||
bucket = bucketParts[0],
|
bucket = bucketParts[0],
|
||||||
appid = bucketParts[1],
|
appid = bucketParts[1],
|
||||||
region = _region,
|
region = region,
|
||||||
key = storagePath,
|
key = storagePath,
|
||||||
httpMethod = "GET",
|
httpMethod = "GET",
|
||||||
isHttps = true,
|
isHttps = true,
|
||||||
signDurationSecond = 3600 * 24
|
signDurationSecond = 3600 * 24
|
||||||
};
|
};
|
||||||
|
|
||||||
var url = _cosXmlClient.GenerateSignURL(request);
|
var url = cosXmlClient.GenerateSignURL(request);
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -207,13 +192,15 @@ public class CosStorageProvider : IStorageProvider
|
|||||||
Directory.CreateDirectory(tempDir);
|
Directory.CreateDirectory(tempDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string bucketName = configService["Storage:CosStorageBucketName"];
|
||||||
string fileName = Path.GetFileName(storagePath);
|
string fileName = Path.GetFileName(storagePath);
|
||||||
string localFilePath = Path.Combine(tempDir, fileName);
|
|
||||||
|
var cosXmlClient = CreateClient();
|
||||||
var transferConfig = new TransferConfig();
|
var transferConfig = new TransferConfig();
|
||||||
var transferManager = new TransferManager(_cosXmlClient, transferConfig);
|
var transferManager = new TransferManager(cosXmlClient, transferConfig);
|
||||||
var downloadTask = new COSXMLDownloadTask(_bucketName, storagePath, tempDir, fileName);
|
var downloadTask = new COSXMLDownloadTask(bucketName, storagePath, tempDir, fileName);
|
||||||
await transferManager.DownloadAsync(downloadTask);
|
await transferManager.DownloadAsync(downloadTask);
|
||||||
return localFilePath;
|
return Path.Combine(tempDir, fileName);
|
||||||
}
|
}
|
||||||
catch (CosClientException clientEx)
|
catch (CosClientException clientEx)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,10 +4,9 @@ using Foxel.Services.Configuration;
|
|||||||
namespace Foxel.Services.Storage.Providers;
|
namespace Foxel.Services.Storage.Providers;
|
||||||
|
|
||||||
[StorageProvider(StorageType.Local)]
|
[StorageProvider(StorageType.Local)]
|
||||||
public class LocalStorageProvider(IConfigService config) : IStorageProvider
|
public class LocalStorageProvider(IConfigService configService) : IStorageProvider
|
||||||
{
|
{
|
||||||
private readonly string _baseDirectory = Path.Combine(Directory.GetCurrentDirectory(), "Uploads");
|
private readonly string _baseDirectory = Path.Combine(Directory.GetCurrentDirectory(), "Uploads");
|
||||||
private readonly string _serverUrl = config["AppSettings:ServerUrl"];
|
|
||||||
|
|
||||||
public async Task<string> SaveAsync(Stream fileStream, string fileName, string contentType)
|
public async Task<string> SaveAsync(Stream fileStream, string fileName, string contentType)
|
||||||
{
|
{
|
||||||
@@ -36,9 +35,10 @@ public class LocalStorageProvider(IConfigService config) : IStorageProvider
|
|||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(storagePath))
|
if (string.IsNullOrEmpty(storagePath))
|
||||||
return $"/images/unavailable.gif";
|
return $"/images/unavailable.gif";
|
||||||
return $"{_serverUrl}{storagePath}";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
string serverUrl = configService["AppSettings:ServerUrl"];
|
||||||
|
return $"{serverUrl}{storagePath}";
|
||||||
|
}
|
||||||
|
|
||||||
public Task<string> DownloadFileAsync(string storagePath)
|
public Task<string> DownloadFileAsync(string storagePath)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,44 +7,31 @@ using Foxel.Services.Configuration;
|
|||||||
namespace Foxel.Services.Storage.Providers;
|
namespace Foxel.Services.Storage.Providers;
|
||||||
|
|
||||||
[StorageProvider(StorageType.S3)]
|
[StorageProvider(StorageType.S3)]
|
||||||
public class S3StorageProvider : IStorageProvider
|
public class S3StorageProvider(IConfigService configService) : IStorageProvider
|
||||||
{
|
{
|
||||||
private readonly string _accessKey;
|
|
||||||
private readonly string _secretKey;
|
|
||||||
private readonly string _bucketName;
|
|
||||||
private readonly string _region;
|
|
||||||
private readonly string _endpoint;
|
|
||||||
private readonly bool _usePathStyleUrls;
|
|
||||||
private readonly string _cdnUrl;
|
|
||||||
|
|
||||||
public S3StorageProvider(IConfigService configService)
|
|
||||||
{
|
|
||||||
_accessKey = configService["Storage:S3StorageAccessKey"];
|
|
||||||
_secretKey = configService["Storage:S3StorageSecretKey"];
|
|
||||||
_bucketName = configService["Storage:S3StorageBucketName"];
|
|
||||||
_region = configService["Storage:S3StorageRegion"];
|
|
||||||
_cdnUrl = configService["Storage:S3StorageCdnUrl"];
|
|
||||||
_endpoint = configService["Storage:S3StorageEndpoint"];
|
|
||||||
_usePathStyleUrls = bool.TryParse(configService["Storage:S3StorageUsePathStyleUrls"], out var usePathStyle) && usePathStyle;
|
|
||||||
}
|
|
||||||
|
|
||||||
private AmazonS3Client CreateClient()
|
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
|
var config = new AmazonS3Config
|
||||||
{
|
{
|
||||||
ServiceURL = _endpoint,
|
ServiceURL = endpoint,
|
||||||
UseHttp = !_endpoint.StartsWith("https", StringComparison.OrdinalIgnoreCase),
|
UseHttp = !endpoint.StartsWith("https", StringComparison.OrdinalIgnoreCase),
|
||||||
ForcePathStyle = _usePathStyleUrls
|
ForcePathStyle = usePathStyleUrls
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(_region) && _endpoint.Contains("amazonaws.com"))
|
if (!string.IsNullOrEmpty(region) && endpoint.Contains("amazonaws.com"))
|
||||||
{
|
{
|
||||||
config.RegionEndpoint = Amazon.RegionEndpoint.GetBySystemName(_region);
|
config.RegionEndpoint = Amazon.RegionEndpoint.GetBySystemName(region);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new AmazonS3Client(
|
return new AmazonS3Client(
|
||||||
_accessKey,
|
accessKey,
|
||||||
_secretKey,
|
secretKey,
|
||||||
config
|
config
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -65,7 +52,7 @@ public class S3StorageProvider : IStorageProvider
|
|||||||
{
|
{
|
||||||
InputStream = fileStream,
|
InputStream = fileStream,
|
||||||
Key = objectKey,
|
Key = objectKey,
|
||||||
BucketName = _bucketName,
|
BucketName = configService["Storage:S3StorageBucketName"],
|
||||||
ContentType = contentType
|
ContentType = contentType
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -91,7 +78,7 @@ public class S3StorageProvider : IStorageProvider
|
|||||||
using var client = CreateClient();
|
using var client = CreateClient();
|
||||||
var deleteRequest = new DeleteObjectRequest
|
var deleteRequest = new DeleteObjectRequest
|
||||||
{
|
{
|
||||||
BucketName = _bucketName,
|
BucketName = configService["Storage:S3StorageBucketName"],
|
||||||
Key = storagePath
|
Key = storagePath
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -110,17 +97,19 @@ public class S3StorageProvider : IStorageProvider
|
|||||||
if (string.IsNullOrEmpty(storagePath))
|
if (string.IsNullOrEmpty(storagePath))
|
||||||
return "/images/unavailable.gif";
|
return "/images/unavailable.gif";
|
||||||
|
|
||||||
|
string cdnUrl = configService["Storage:S3StorageCdnUrl"];
|
||||||
|
|
||||||
// 如果配置了CDN URL,使用CDN
|
// 如果配置了CDN URL,使用CDN
|
||||||
if (!string.IsNullOrEmpty(_cdnUrl))
|
if (!string.IsNullOrEmpty(cdnUrl))
|
||||||
{
|
{
|
||||||
return $"{_cdnUrl}/{storagePath}";
|
return $"{cdnUrl}/{storagePath}";
|
||||||
}
|
}
|
||||||
|
|
||||||
// 否则使用S3直链或生成预签名URL
|
// 否则使用S3直链或生成预签名URL
|
||||||
using var client = CreateClient();
|
using var client = CreateClient();
|
||||||
var request = new GetPreSignedUrlRequest
|
var request = new GetPreSignedUrlRequest
|
||||||
{
|
{
|
||||||
BucketName = _bucketName,
|
BucketName = configService["Storage:S3StorageBucketName"],
|
||||||
Key = storagePath,
|
Key = storagePath,
|
||||||
Expires = DateTime.UtcNow.AddHours(1) // URL有效期1小时
|
Expires = DateTime.UtcNow.AddHours(1) // URL有效期1小时
|
||||||
};
|
};
|
||||||
@@ -158,7 +147,7 @@ public class S3StorageProvider : IStorageProvider
|
|||||||
using var client = CreateClient();
|
using var client = CreateClient();
|
||||||
var request = new GetObjectRequest
|
var request = new GetObjectRequest
|
||||||
{
|
{
|
||||||
BucketName = _bucketName,
|
BucketName = configService["Storage:S3StorageBucketName"],
|
||||||
Key = storagePath
|
Key = storagePath
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -9,15 +9,14 @@ namespace Foxel.Services.Storage.Providers;
|
|||||||
[StorageProvider(StorageType.Telegram)]
|
[StorageProvider(StorageType.Telegram)]
|
||||||
public class TelegramStorageProvider(IConfigService configService) : IStorageProvider
|
public class TelegramStorageProvider(IConfigService configService) : IStorageProvider
|
||||||
{
|
{
|
||||||
private readonly string _botToken = configService["Storage:TelegramStorageBotToken"];
|
|
||||||
private readonly string _chatId = configService["Storage:TelegramStorageChatId"];
|
|
||||||
private readonly string _serverUrl = configService["AppSettings:ServerUrl"];
|
|
||||||
|
|
||||||
public async Task<string> SaveAsync(Stream fileStream, string fileName, string contentType)
|
public async Task<string> SaveAsync(Stream fileStream, string fileName, string contentType)
|
||||||
{
|
{
|
||||||
|
string botToken = configService["Storage:TelegramStorageBotToken"];
|
||||||
|
string chatId = configService["Storage:TelegramStorageChatId"];
|
||||||
|
|
||||||
using var httpClient = new HttpClient();
|
using var httpClient = new HttpClient();
|
||||||
using var formData = new MultipartFormDataContent();
|
using var formData = new MultipartFormDataContent();
|
||||||
formData.Add(new StringContent(_chatId), "chat_id");
|
formData.Add(new StringContent(chatId), "chat_id");
|
||||||
var safeFileName = Path.GetFileNameWithoutExtension(fileName);
|
var safeFileName = Path.GetFileNameWithoutExtension(fileName);
|
||||||
if (safeFileName.Length > 100)
|
if (safeFileName.Length > 100)
|
||||||
safeFileName = safeFileName.Substring(0, 100);
|
safeFileName = safeFileName.Substring(0, 100);
|
||||||
@@ -35,7 +34,7 @@ public class TelegramStorageProvider(IConfigService configService) : IStoragePro
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response =
|
var response =
|
||||||
await httpClient.PostAsync($"https://api.telegram.org/bot{_botToken}/sendDocument", formData);
|
await httpClient.PostAsync($"https://api.telegram.org/bot{botToken}/sendDocument", formData);
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
@@ -58,7 +57,7 @@ public class TelegramStorageProvider(IConfigService configService) : IStoragePro
|
|||||||
FileId = fileId,
|
FileId = fileId,
|
||||||
FileUniqueId = responseObj.Result.Document.FileUniqueId,
|
FileUniqueId = responseObj.Result.Document.FileUniqueId,
|
||||||
MessageId = responseObj.Result.MessageId,
|
MessageId = responseObj.Result.MessageId,
|
||||||
ChatId = _chatId,
|
ChatId = chatId,
|
||||||
OriginalFileName = fileName,
|
OriginalFileName = fileName,
|
||||||
UploadDate = DateTime.UtcNow,
|
UploadDate = DateTime.UtcNow,
|
||||||
MimeType = contentType
|
MimeType = contentType
|
||||||
@@ -82,9 +81,11 @@ public class TelegramStorageProvider(IConfigService configService) : IStoragePro
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string botToken = configService["Storage:TelegramStorageBotToken"];
|
||||||
|
|
||||||
using var httpClient = new HttpClient();
|
using var httpClient = new HttpClient();
|
||||||
var url =
|
var url =
|
||||||
$"https://api.telegram.org/bot{_botToken}/deleteMessage?chat_id={metadata.ChatId}&message_id={metadata.MessageId}";
|
$"https://api.telegram.org/bot{botToken}/deleteMessage?chat_id={metadata.ChatId}&message_id={metadata.MessageId}";
|
||||||
await httpClient.GetAsync(url);
|
await httpClient.GetAsync(url);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -103,7 +104,8 @@ public class TelegramStorageProvider(IConfigService configService) : IStoragePro
|
|||||||
throw new ApplicationException("无效的存储路径或元数据");
|
throw new ApplicationException("无效的存储路径或元数据");
|
||||||
}
|
}
|
||||||
|
|
||||||
return $"{_serverUrl}/api/picture/get_telegram_file?fileId={metadata.FileId}";
|
string serverUrl = configService["AppSettings:ServerUrl"];
|
||||||
|
return $"{serverUrl}/api/picture/get_telegram_file?fileId={metadata.FileId}";
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -127,8 +129,10 @@ public class TelegramStorageProvider(IConfigService configService) : IStoragePro
|
|||||||
throw new ApplicationException("无效的存储路径或元数据");
|
throw new ApplicationException("无效的存储路径或元数据");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string botToken = configService["Storage:TelegramStorageBotToken"];
|
||||||
|
|
||||||
using var httpClient = new HttpClient();
|
using var httpClient = new HttpClient();
|
||||||
var getFileUrl = $"https://api.telegram.org/bot{_botToken}/getFile?file_id={metadata.FileId}";
|
var getFileUrl = $"https://api.telegram.org/bot{botToken}/getFile?file_id={metadata.FileId}";
|
||||||
var getFileResponse = await httpClient.GetAsync(getFileUrl);
|
var getFileResponse = await httpClient.GetAsync(getFileUrl);
|
||||||
|
|
||||||
if (!getFileResponse.IsSuccessStatusCode)
|
if (!getFileResponse.IsSuccessStatusCode)
|
||||||
@@ -145,7 +149,7 @@ public class TelegramStorageProvider(IConfigService configService) : IStoragePro
|
|||||||
}
|
}
|
||||||
|
|
||||||
var filePath = getFileResult.Result.FilePath;
|
var filePath = getFileResult.Result.FilePath;
|
||||||
var fileUrl = $"https://api.telegram.org/file/bot{_botToken}/{filePath}";
|
var fileUrl = $"https://api.telegram.org/file/bot{botToken}/{filePath}";
|
||||||
|
|
||||||
var fileResponse = await httpClient.GetAsync(fileUrl);
|
var fileResponse = await httpClient.GetAsync(fileUrl);
|
||||||
if (!fileResponse.IsSuccessStatusCode)
|
if (!fileResponse.IsSuccessStatusCode)
|
||||||
|
|||||||
@@ -6,52 +6,49 @@ using Foxel.Services.Configuration;
|
|||||||
namespace Foxel.Services.Storage.Providers;
|
namespace Foxel.Services.Storage.Providers;
|
||||||
|
|
||||||
[StorageProvider(StorageType.WebDAV)]
|
[StorageProvider(StorageType.WebDAV)]
|
||||||
public class WebDavStorageProvider : IStorageProvider
|
public class WebDavStorageProvider(IConfigService configService) : IStorageProvider
|
||||||
{
|
{
|
||||||
private readonly string _webDavServerUrl;
|
private HttpClient CreateClient()
|
||||||
private readonly string _serverUrl;
|
|
||||||
private readonly string _basePath;
|
|
||||||
private readonly string _publicUrl;
|
|
||||||
private readonly HttpClient _httpClient;
|
|
||||||
|
|
||||||
public WebDavStorageProvider(IConfigService configService)
|
|
||||||
{
|
{
|
||||||
_webDavServerUrl = configService["Storage:WebDAVServerUrl"].TrimEnd('/');
|
var httpClient = new HttpClient();
|
||||||
var userName = configService["Storage:WebDAVUserName"];
|
var userName = configService["Storage:WebDAVUserName"];
|
||||||
var password = configService["Storage:WebDAVPassword"];
|
var password = configService["Storage:WebDAVPassword"];
|
||||||
_basePath = configService["Storage:WebDAVBasePath"].Trim('/');
|
|
||||||
_publicUrl = configService["Storage:WebDAVPublicUrl"].TrimEnd('/');
|
|
||||||
_serverUrl = configService["AppSettings:ServerUrl"];
|
|
||||||
_httpClient = new HttpClient();
|
|
||||||
if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password))
|
if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password))
|
||||||
{
|
{
|
||||||
var authValue = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{userName}:{password}"));
|
var authValue = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{userName}:{password}"));
|
||||||
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authValue);
|
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return httpClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> SaveAsync(Stream fileStream, string fileName, string contentType)
|
public async Task<string> SaveAsync(Stream fileStream, string fileName, string contentType)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
string webDavServerUrl = configService["Storage:WebDAVServerUrl"].TrimEnd('/');
|
||||||
|
string basePath = configService["Storage:WebDAVBasePath"].Trim('/');
|
||||||
|
|
||||||
// 创建唯一的文件存储路径
|
// 创建唯一的文件存储路径
|
||||||
string currentDate = DateTime.Now.ToString("yyyy/MM");
|
string currentDate = DateTime.Now.ToString("yyyy/MM");
|
||||||
string ext = Path.GetExtension(fileName);
|
string ext = Path.GetExtension(fileName);
|
||||||
string newFileName = $"{Guid.NewGuid()}{ext}";
|
string newFileName = $"{Guid.NewGuid()}{ext}";
|
||||||
string relativePath = $"{_basePath}/{currentDate}/{newFileName}";
|
string relativePath = $"{basePath}/{currentDate}/{newFileName}";
|
||||||
|
|
||||||
// 确保目录存在
|
// 确保目录存在
|
||||||
await EnsureDirectoryExistsAsync($"{_basePath}/{currentDate}");
|
await EnsureDirectoryExistsAsync($"{basePath}/{currentDate}");
|
||||||
|
|
||||||
// 上传文件内容
|
// 上传文件内容
|
||||||
var requestUri = $"{_webDavServerUrl}/{relativePath}";
|
var requestUri = $"{webDavServerUrl}/{relativePath}";
|
||||||
|
using var client = CreateClient();
|
||||||
using var content = new StreamContent(fileStream);
|
using var content = new StreamContent(fileStream);
|
||||||
content.Headers.ContentType = new MediaTypeHeaderValue(contentType);
|
content.Headers.ContentType = new MediaTypeHeaderValue(contentType);
|
||||||
|
|
||||||
using var request = new HttpRequestMessage(HttpMethod.Put, requestUri);
|
using var request = new HttpRequestMessage(HttpMethod.Put, requestUri);
|
||||||
request.Content = content;
|
request.Content = content;
|
||||||
|
|
||||||
var response = await _httpClient.SendAsync(request);
|
var response = await client.SendAsync(request);
|
||||||
response.EnsureSuccessStatusCode();
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
return relativePath;
|
return relativePath;
|
||||||
@@ -70,8 +67,11 @@ public class WebDavStorageProvider : IStorageProvider
|
|||||||
if (string.IsNullOrEmpty(storagePath))
|
if (string.IsNullOrEmpty(storagePath))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var requestUri = $"{_webDavServerUrl}/{storagePath}";
|
string webDavServerUrl = configService["Storage:WebDAVServerUrl"].TrimEnd('/');
|
||||||
var response = await _httpClient.DeleteAsync(requestUri);
|
var requestUri = $"{webDavServerUrl}/{storagePath}";
|
||||||
|
|
||||||
|
using var client = CreateClient();
|
||||||
|
var response = await client.DeleteAsync(requestUri);
|
||||||
|
|
||||||
if (response.StatusCode != System.Net.HttpStatusCode.NotFound)
|
if (response.StatusCode != System.Net.HttpStatusCode.NotFound)
|
||||||
{
|
{
|
||||||
@@ -91,12 +91,15 @@ public class WebDavStorageProvider : IStorageProvider
|
|||||||
if (string.IsNullOrEmpty(storagePath))
|
if (string.IsNullOrEmpty(storagePath))
|
||||||
return "/images/unavailable.gif";
|
return "/images/unavailable.gif";
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(_publicUrl))
|
string publicUrl = configService["Storage:WebDAVPublicUrl"].TrimEnd('/');
|
||||||
|
string serverUrl = configService["AppSettings:ServerUrl"];
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(publicUrl))
|
||||||
{
|
{
|
||||||
return $"{_publicUrl}/{storagePath}";
|
return $"{publicUrl}/{storagePath}";
|
||||||
}
|
}
|
||||||
|
|
||||||
return $"{_serverUrl}/api/picture/proxy?path={Uri.EscapeDataString(storagePath)}";
|
return $"{serverUrl}/api/picture/proxy?path={Uri.EscapeDataString(storagePath)}";
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -114,6 +117,8 @@ public class WebDavStorageProvider : IStorageProvider
|
|||||||
throw new ArgumentException("存储路径不能为空");
|
throw new ArgumentException("存储路径不能为空");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string webDavServerUrl = configService["Storage:WebDAVServerUrl"].TrimEnd('/');
|
||||||
|
|
||||||
// 创建临时目录
|
// 创建临时目录
|
||||||
var tempDir = Path.Combine(Path.GetTempPath(), "FoxelWebDAVTemp");
|
var tempDir = Path.Combine(Path.GetTempPath(), "FoxelWebDAVTemp");
|
||||||
if (!Directory.Exists(tempDir))
|
if (!Directory.Exists(tempDir))
|
||||||
@@ -126,9 +131,10 @@ public class WebDavStorageProvider : IStorageProvider
|
|||||||
string tempFilePath = Path.Combine(tempDir, fileName);
|
string tempFilePath = Path.Combine(tempDir, fileName);
|
||||||
|
|
||||||
// 下载文件
|
// 下载文件
|
||||||
var requestUri = $"{_webDavServerUrl}/{storagePath}";
|
var requestUri = $"{webDavServerUrl}/{storagePath}";
|
||||||
|
using var client = CreateClient();
|
||||||
using var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
|
using var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
|
||||||
var response = await _httpClient.SendAsync(request);
|
var response = await client.SendAsync(request);
|
||||||
response.EnsureSuccessStatusCode();
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
await using var fileStream = new FileStream(tempFilePath, FileMode.Create);
|
await using var fileStream = new FileStream(tempFilePath, FileMode.Create);
|
||||||
@@ -150,18 +156,20 @@ public class WebDavStorageProvider : IStorageProvider
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var requestUri = $"{_webDavServerUrl}/{directoryPath}";
|
string webDavServerUrl = configService["Storage:WebDAVServerUrl"].TrimEnd('/');
|
||||||
|
var requestUri = $"{webDavServerUrl}/{directoryPath}";
|
||||||
|
using var client = CreateClient();
|
||||||
|
|
||||||
// 检查目录是否存在 - 使用新的请求对象
|
// 检查目录是否存在 - 使用新的请求对象
|
||||||
using var headRequest = new HttpRequestMessage(HttpMethod.Head, requestUri);
|
using var headRequest = new HttpRequestMessage(HttpMethod.Head, requestUri);
|
||||||
var response = await _httpClient.SendAsync(headRequest);
|
var response = await client.SendAsync(headRequest);
|
||||||
|
|
||||||
if (response.IsSuccessStatusCode)
|
if (response.IsSuccessStatusCode)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// 创建目录 - 使用新的请求对象
|
// 创建目录 - 使用新的请求对象
|
||||||
using var mkcolRequest = new HttpRequestMessage(new HttpMethod("MKCOL"), requestUri);
|
using var mkcolRequest = new HttpRequestMessage(new HttpMethod("MKCOL"), requestUri);
|
||||||
response = await _httpClient.SendAsync(mkcolRequest);
|
response = await client.SendAsync(mkcolRequest);
|
||||||
|
|
||||||
// 处理状态码
|
// 处理状态码
|
||||||
if (response.StatusCode == System.Net.HttpStatusCode.Conflict ||
|
if (response.StatusCode == System.Net.HttpStatusCode.Conflict ||
|
||||||
@@ -174,13 +182,13 @@ public class WebDavStorageProvider : IStorageProvider
|
|||||||
await EnsureDirectoryExistsAsync(parentPath);
|
await EnsureDirectoryExistsAsync(parentPath);
|
||||||
|
|
||||||
using var retryRequest = new HttpRequestMessage(new HttpMethod("MKCOL"), requestUri);
|
using var retryRequest = new HttpRequestMessage(new HttpMethod("MKCOL"), requestUri);
|
||||||
response = await _httpClient.SendAsync(retryRequest);
|
response = await client.SendAsync(retryRequest);
|
||||||
|
|
||||||
if (response.StatusCode == System.Net.HttpStatusCode.MethodNotAllowed)
|
if (response.StatusCode == System.Net.HttpStatusCode.MethodNotAllowed)
|
||||||
{
|
{
|
||||||
using var putRequest = new HttpRequestMessage(HttpMethod.Put, $"{requestUri}/.dummy");
|
using var putRequest = new HttpRequestMessage(HttpMethod.Put, $"{requestUri}/.dummy");
|
||||||
putRequest.Content = new StringContent(string.Empty);
|
putRequest.Content = new StringContent(string.Empty);
|
||||||
await _httpClient.SendAsync(putRequest);
|
await client.SendAsync(putRequest);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -192,7 +200,7 @@ public class WebDavStorageProvider : IStorageProvider
|
|||||||
{
|
{
|
||||||
using var putRequest = new HttpRequestMessage(HttpMethod.Put, $"{requestUri}/.dummy");
|
using var putRequest = new HttpRequestMessage(HttpMethod.Put, $"{requestUri}/.dummy");
|
||||||
putRequest.Content = new StringContent(string.Empty);
|
putRequest.Content = new StringContent(string.Empty);
|
||||||
var putResponse = await _httpClient.SendAsync(putRequest);
|
var putResponse = await client.SendAsync(putRequest);
|
||||||
putResponse.EnsureSuccessStatusCode();
|
putResponse.EnsureSuccessStatusCode();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -137,21 +137,43 @@ const SystemConfig: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</TabPane>
|
</TabPane>
|
||||||
|
|
||||||
<TabPane tab="JWT 设置" key="Jwt">
|
<TabPane tab="授权配置" key="Authorization">
|
||||||
<ConfigGroup
|
<Tabs defaultActiveKey="jwt" type="card" size={isMobile ? "small" : "middle"}>
|
||||||
groupName="Jwt"
|
<TabPane tab="JWT 设置" key="jwt">
|
||||||
configs={{
|
<ConfigGroup
|
||||||
SecretKey: configs.Jwt?.SecretKey || '',
|
groupName="Jwt"
|
||||||
Issuer: configs.Jwt?.Issuer || '',
|
configs={{
|
||||||
Audience: configs.Jwt?.Audience || '',
|
SecretKey: configs.Jwt?.SecretKey || '',
|
||||||
}}
|
Issuer: configs.Jwt?.Issuer || '',
|
||||||
onSave={handleSaveConfig}
|
Audience: configs.Jwt?.Audience || '',
|
||||||
descriptions={{
|
}}
|
||||||
SecretKey: 'JWT 加密密钥',
|
onSave={handleSaveConfig}
|
||||||
Issuer: 'JWT 签发者',
|
descriptions={{
|
||||||
Audience: 'JWT 接收者',
|
SecretKey: 'JWT 加密密钥',
|
||||||
}}
|
Issuer: 'JWT 签发者',
|
||||||
/>
|
Audience: 'JWT 接收者',
|
||||||
|
}}
|
||||||
|
isMobile={isMobile}
|
||||||
|
/>
|
||||||
|
</TabPane>
|
||||||
|
<TabPane tab="GitHub认证" key="github">
|
||||||
|
<ConfigGroup
|
||||||
|
groupName="Authentication"
|
||||||
|
configs={{
|
||||||
|
"GitHubClientId": configs.Authentication?.["GitHubClientId"] || '',
|
||||||
|
"GitHubClientSecret": configs.Authentication?.["GitHubClientSecret"] || '',
|
||||||
|
"GitHubCallbackUrl": configs.Authentication?.["GitHubCallbackUrl"] || ''
|
||||||
|
}}
|
||||||
|
onSave={(_group, key, value) => handleSaveConfig('Authentication', key, value)}
|
||||||
|
descriptions={{
|
||||||
|
"GitHubClientId": 'GitHub OAuth 应用客户端ID',
|
||||||
|
"GitHubClientSecret": 'GitHub OAuth 应用客户端密钥',
|
||||||
|
"GitHubCallbackUrl": 'GitHub OAuth 认证回调地址'
|
||||||
|
}}
|
||||||
|
isMobile={isMobile}
|
||||||
|
/>
|
||||||
|
</TabPane>
|
||||||
|
</Tabs>
|
||||||
</TabPane>
|
</TabPane>
|
||||||
|
|
||||||
<TabPane tab="应用设置" key="AppSettings">
|
<TabPane tab="应用设置" key="AppSettings">
|
||||||
@@ -167,23 +189,6 @@ const SystemConfig: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</TabPane>
|
</TabPane>
|
||||||
|
|
||||||
<TabPane tab="GitHub认证" key="Authentication">
|
|
||||||
<ConfigGroup
|
|
||||||
groupName="Authentication"
|
|
||||||
configs={{
|
|
||||||
"GitHubClientId": configs.Authentication?.["GitHubClientId"] || '',
|
|
||||||
"GitHubClientSecret": configs.Authentication?.["GitHubClientSecret"] || '',
|
|
||||||
"GitHubCallbackUrl": configs.Authentication?.["GitHubCallbackUrl"] || ''
|
|
||||||
}}
|
|
||||||
onSave={(_group, key, value) => handleSaveConfig('Authentication', key, value)}
|
|
||||||
descriptions={{
|
|
||||||
"GitHubClientId": 'GitHub OAuth 应用客户端ID',
|
|
||||||
"GitHubClientSecret": 'GitHub OAuth 应用客户端密钥',
|
|
||||||
"GitHubCallbackUrl": 'GitHub OAuth 认证回调地址'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</TabPane>
|
|
||||||
|
|
||||||
<TabPane tab="存储设置" key="Storage">
|
<TabPane tab="存储设置" key="Storage">
|
||||||
<div style={{
|
<div style={{
|
||||||
marginBottom: isMobile ? 16 : 20,
|
marginBottom: isMobile ? 16 : 20,
|
||||||
|
|||||||
Reference in New Issue
Block a user