refactor(storage): update storage providers to use IConfigService for configuration management

This commit is contained in:
ShiYu
2025-05-22 23:13:03 +08:00
parent 93fb2db208
commit a03e245d67
7 changed files with 167 additions and 158 deletions
+19 -3
View File
@@ -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,10 +92,13 @@ public class AiService : IAiService
{ {
try try
{ {
// 确保使用最新配置
ConfigureHttpClient();
if (availableTags.Count == 0) if (availableTags.Count == 0)
return new List<string>(); return new List<string>();
string model = _configService["AI:Model"]; string model = _configService["AI:Model"];
var tagsText = string.Join(", ", availableTags); var tagsText = string.Join(", ", availableTags);
var textContent = new TextContent var textContent = new TextContent
{ {
@@ -189,8 +201,10 @@ 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
{ {
@@ -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)
{ {
+22 -33
View File
@@ -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
+37 -32
View File
@@ -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,