diff --git a/Services/AI/AiService.cs b/Services/AI/AiService.cs index fbaa515..fdaf9ea 100644 --- a/Services/AI/AiService.cs +++ b/Services/AI/AiService.cs @@ -14,9 +14,17 @@ public class AiService : IAiService { _httpClient = httpClient; _configService = configService; + } + + private void ConfigureHttpClient() + { string apiKey = _configService["AI:ApiKey"]; 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); } @@ -24,6 +32,7 @@ public class AiService : IAiService { try { + ConfigureHttpClient(); string model = _configService["AI:Model"]; var imageUrl = new ImageUrl { @@ -83,10 +92,13 @@ public class AiService : IAiService { try { + // 确保使用最新配置 + ConfigureHttpClient(); + if (availableTags.Count == 0) return new List(); - string model = _configService["AI:Model"]; + string model = _configService["AI:Model"]; var tagsText = string.Join(", ", availableTags); var textContent = new TextContent { @@ -189,8 +201,10 @@ public class AiService : IAiService { try { + // 确保使用最新配置 + ConfigureHttpClient(); - string model = _configService["AI:Model"]; + string model = _configService["AI:Model"]; var imageUrl = new ImageUrl { @@ -329,6 +343,8 @@ public class AiService : IAiService { try { + // 确保使用最新配置 + ConfigureHttpClient(); string model = _configService["AI:EmbeddingModel"]; diff --git a/Services/Storage/Providers/CosStorageProvider.cs b/Services/Storage/Providers/CosStorageProvider.cs index e2ccebf..2f3af2c 100644 --- a/Services/Storage/Providers/CosStorageProvider.cs +++ b/Services/Storage/Providers/CosStorageProvider.cs @@ -39,42 +39,19 @@ public class CustomQCloudCredentialProvider : DefaultSessionQCloudCredentialProv } } } + [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() { - // 优化配置:启用HTTPS和日志 var config = new CosXmlConfig.Builder() - .IsHttps(true) // 设置默认HTTPS请求 - .SetRegion(_region) - .SetDebugLog(true) // 显示日志 + .IsHttps(true) + .SetRegion(configService["Storage:CosStorageRegion"]) + .SetDebugLog(true) .Build(); - // 使用自定义凭证提供者,支持持续更新临时密钥 - var cosCredentialProvider = new CustomQCloudCredentialProvider(_configService); + var cosCredentialProvider = new CustomQCloudCredentialProvider(configService); return new CosXmlServer(config, cosCredentialProvider); } @@ -97,9 +74,10 @@ public class CosStorageProvider : IStorageProvider await fileStream.CopyToAsync(fileStream2); } + var cosXmlClient = CreateClient(); var transferConfig = new TransferConfig(); - var transferManager = new TransferManager(_cosXmlClient, transferConfig); - var uploadTask = new COSXMLUploadTask(_bucketName, objectKey); + var transferManager = new TransferManager(cosXmlClient, transferConfig); + var uploadTask = new COSXMLUploadTask(configService["Storage:CosStorageBucketName"], objectKey); uploadTask.SetSrcPath(tempPath); await transferManager.UploadAsync(uploadTask); return objectKey; @@ -137,8 +115,9 @@ public class CosStorageProvider : IStorageProvider if (string.IsNullOrEmpty(storagePath)) return; - var request = new DeleteObjectRequest(_bucketName, storagePath); - await Task.Run(() => _cosXmlClient.DeleteObject(request)); + var cosXmlClient = CreateClient(); + var request = new DeleteObjectRequest(configService["Storage:CosStorageBucketName"], storagePath); + await Task.Run(() => cosXmlClient.DeleteObject(request)); } catch (CosClientException clientEx) { @@ -161,27 +140,33 @@ public class CosStorageProvider : IStorageProvider if (string.IsNullOrEmpty(storagePath)) 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 - if (!string.IsNullOrEmpty(_cdnUrl)) - return $"{_cdnUrl}/{storagePath}"; + if (!string.IsNullOrEmpty(cdnUrl)) + return $"{cdnUrl}/{storagePath}"; // 公开读取的桶可直接访问 - if (_isPublicRead) - return $"https://{_bucketName}.cos.{_region}.myqcloud.com/{storagePath}"; + if (isPublicRead) + return $"https://{bucketName}.cos.{region}.myqcloud.com/{storagePath}"; - var bucketParts = _bucketName.Split('-'); + var cosXmlClient = CreateClient(); + var bucketParts = bucketName.Split('-'); var request = new PreSignatureStruct { bucket = bucketParts[0], appid = bucketParts[1], - region = _region, + region = region, key = storagePath, httpMethod = "GET", isHttps = true, signDurationSecond = 3600 * 24 }; - var url = _cosXmlClient.GenerateSignURL(request); + var url = cosXmlClient.GenerateSignURL(request); return url; } catch (Exception ex) @@ -207,13 +192,15 @@ public class CosStorageProvider : IStorageProvider Directory.CreateDirectory(tempDir); } + string bucketName = configService["Storage:CosStorageBucketName"]; string fileName = Path.GetFileName(storagePath); - string localFilePath = Path.Combine(tempDir, fileName); + + var cosXmlClient = CreateClient(); var transferConfig = new TransferConfig(); - var transferManager = new TransferManager(_cosXmlClient, transferConfig); - var downloadTask = new COSXMLDownloadTask(_bucketName, storagePath, tempDir, fileName); + var transferManager = new TransferManager(cosXmlClient, transferConfig); + var downloadTask = new COSXMLDownloadTask(bucketName, storagePath, tempDir, fileName); await transferManager.DownloadAsync(downloadTask); - return localFilePath; + return Path.Combine(tempDir, fileName); } catch (CosClientException clientEx) { diff --git a/Services/Storage/Providers/LocalStorageProvider.cs b/Services/Storage/Providers/LocalStorageProvider.cs index 24fc94a..6dcd3d3 100644 --- a/Services/Storage/Providers/LocalStorageProvider.cs +++ b/Services/Storage/Providers/LocalStorageProvider.cs @@ -4,10 +4,9 @@ using Foxel.Services.Configuration; namespace Foxel.Services.Storage.Providers; [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 _serverUrl = config["AppSettings:ServerUrl"]; public async Task SaveAsync(Stream fileStream, string fileName, string contentType) { @@ -36,9 +35,10 @@ public class LocalStorageProvider(IConfigService config) : IStorageProvider { if (string.IsNullOrEmpty(storagePath)) return $"/images/unavailable.gif"; - return $"{_serverUrl}{storagePath}"; - } + string serverUrl = configService["AppSettings:ServerUrl"]; + return $"{serverUrl}{storagePath}"; + } public Task DownloadFileAsync(string storagePath) { diff --git a/Services/Storage/Providers/S3StorageProvider.cs b/Services/Storage/Providers/S3StorageProvider.cs index a768c05..966d280 100644 --- a/Services/Storage/Providers/S3StorageProvider.cs +++ b/Services/Storage/Providers/S3StorageProvider.cs @@ -7,44 +7,31 @@ using Foxel.Services.Configuration; namespace Foxel.Services.Storage.Providers; [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() { + 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 + ServiceURL = endpoint, + UseHttp = !endpoint.StartsWith("https", StringComparison.OrdinalIgnoreCase), + 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( - _accessKey, - _secretKey, + accessKey, + secretKey, config ); } @@ -65,7 +52,7 @@ public class S3StorageProvider : IStorageProvider { InputStream = fileStream, Key = objectKey, - BucketName = _bucketName, + BucketName = configService["Storage:S3StorageBucketName"], ContentType = contentType }; @@ -91,7 +78,7 @@ public class S3StorageProvider : IStorageProvider using var client = CreateClient(); var deleteRequest = new DeleteObjectRequest { - BucketName = _bucketName, + BucketName = configService["Storage:S3StorageBucketName"], Key = storagePath }; @@ -110,17 +97,19 @@ public class S3StorageProvider : IStorageProvider if (string.IsNullOrEmpty(storagePath)) return "/images/unavailable.gif"; + string cdnUrl = configService["Storage:S3StorageCdnUrl"]; + // 如果配置了CDN URL,使用CDN - if (!string.IsNullOrEmpty(_cdnUrl)) + if (!string.IsNullOrEmpty(cdnUrl)) { - return $"{_cdnUrl}/{storagePath}"; + return $"{cdnUrl}/{storagePath}"; } // 否则使用S3直链或生成预签名URL using var client = CreateClient(); var request = new GetPreSignedUrlRequest { - BucketName = _bucketName, + BucketName = configService["Storage:S3StorageBucketName"], Key = storagePath, Expires = DateTime.UtcNow.AddHours(1) // URL有效期1小时 }; @@ -158,7 +147,7 @@ public class S3StorageProvider : IStorageProvider using var client = CreateClient(); var request = new GetObjectRequest { - BucketName = _bucketName, + BucketName = configService["Storage:S3StorageBucketName"], Key = storagePath }; diff --git a/Services/Storage/Providers/TelegramStorageProvider.cs b/Services/Storage/Providers/TelegramStorageProvider.cs index b1809b1..eb152aa 100644 --- a/Services/Storage/Providers/TelegramStorageProvider.cs +++ b/Services/Storage/Providers/TelegramStorageProvider.cs @@ -9,15 +9,14 @@ namespace Foxel.Services.Storage.Providers; [StorageProvider(StorageType.Telegram)] 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 SaveAsync(Stream fileStream, string fileName, string contentType) { + string botToken = configService["Storage:TelegramStorageBotToken"]; + string chatId = configService["Storage:TelegramStorageChatId"]; + using var httpClient = new HttpClient(); using var formData = new MultipartFormDataContent(); - formData.Add(new StringContent(_chatId), "chat_id"); + formData.Add(new StringContent(chatId), "chat_id"); var safeFileName = Path.GetFileNameWithoutExtension(fileName); if (safeFileName.Length > 100) safeFileName = safeFileName.Substring(0, 100); @@ -35,7 +34,7 @@ public class TelegramStorageProvider(IConfigService configService) : IStoragePro try { 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) { @@ -58,7 +57,7 @@ public class TelegramStorageProvider(IConfigService configService) : IStoragePro FileId = fileId, FileUniqueId = responseObj.Result.Document.FileUniqueId, MessageId = responseObj.Result.MessageId, - ChatId = _chatId, + ChatId = chatId, OriginalFileName = fileName, UploadDate = DateTime.UtcNow, MimeType = contentType @@ -82,9 +81,11 @@ public class TelegramStorageProvider(IConfigService configService) : IStoragePro return; } + string botToken = configService["Storage:TelegramStorageBotToken"]; + using var httpClient = new HttpClient(); 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); } catch (Exception ex) @@ -103,7 +104,8 @@ public class TelegramStorageProvider(IConfigService configService) : IStoragePro 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) { @@ -127,8 +129,10 @@ public class TelegramStorageProvider(IConfigService configService) : IStoragePro throw new ApplicationException("无效的存储路径或元数据"); } + string botToken = configService["Storage:TelegramStorageBotToken"]; + 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); if (!getFileResponse.IsSuccessStatusCode) @@ -145,7 +149,7 @@ public class TelegramStorageProvider(IConfigService configService) : IStoragePro } 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); if (!fileResponse.IsSuccessStatusCode) diff --git a/Services/Storage/Providers/WebDAVStorageProvider.cs b/Services/Storage/Providers/WebDAVStorageProvider.cs index 5010819..ded01fa 100644 --- a/Services/Storage/Providers/WebDAVStorageProvider.cs +++ b/Services/Storage/Providers/WebDAVStorageProvider.cs @@ -6,52 +6,49 @@ using Foxel.Services.Configuration; namespace Foxel.Services.Storage.Providers; [StorageProvider(StorageType.WebDAV)] -public class WebDavStorageProvider : IStorageProvider +public class WebDavStorageProvider(IConfigService configService) : IStorageProvider { - private readonly string _webDavServerUrl; - private readonly string _serverUrl; - private readonly string _basePath; - private readonly string _publicUrl; - private readonly HttpClient _httpClient; - - public WebDavStorageProvider(IConfigService configService) + private HttpClient CreateClient() { - _webDavServerUrl = configService["Storage:WebDAVServerUrl"].TrimEnd('/'); + var httpClient = new HttpClient(); var userName = configService["Storage:WebDAVUserName"]; 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)) { 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 SaveAsync(Stream fileStream, string fileName, string contentType) { try { + string webDavServerUrl = configService["Storage:WebDAVServerUrl"].TrimEnd('/'); + string basePath = configService["Storage:WebDAVBasePath"].Trim('/'); + // 创建唯一的文件存储路径 string currentDate = DateTime.Now.ToString("yyyy/MM"); string ext = Path.GetExtension(fileName); 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); content.Headers.ContentType = new MediaTypeHeaderValue(contentType); using var request = new HttpRequestMessage(HttpMethod.Put, requestUri); request.Content = content; - var response = await _httpClient.SendAsync(request); + var response = await client.SendAsync(request); response.EnsureSuccessStatusCode(); return relativePath; @@ -70,8 +67,11 @@ public class WebDavStorageProvider : IStorageProvider if (string.IsNullOrEmpty(storagePath)) return; - var requestUri = $"{_webDavServerUrl}/{storagePath}"; - var response = await _httpClient.DeleteAsync(requestUri); + string webDavServerUrl = configService["Storage:WebDAVServerUrl"].TrimEnd('/'); + var requestUri = $"{webDavServerUrl}/{storagePath}"; + + using var client = CreateClient(); + var response = await client.DeleteAsync(requestUri); if (response.StatusCode != System.Net.HttpStatusCode.NotFound) { @@ -91,12 +91,15 @@ public class WebDavStorageProvider : IStorageProvider if (string.IsNullOrEmpty(storagePath)) 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) { @@ -114,6 +117,8 @@ public class WebDavStorageProvider : IStorageProvider throw new ArgumentException("存储路径不能为空"); } + string webDavServerUrl = configService["Storage:WebDAVServerUrl"].TrimEnd('/'); + // 创建临时目录 var tempDir = Path.Combine(Path.GetTempPath(), "FoxelWebDAVTemp"); if (!Directory.Exists(tempDir)) @@ -126,9 +131,10 @@ public class WebDavStorageProvider : IStorageProvider 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); - var response = await _httpClient.SendAsync(request); + var response = await client.SendAsync(request); response.EnsureSuccessStatusCode(); await using var fileStream = new FileStream(tempFilePath, FileMode.Create); @@ -150,18 +156,20 @@ public class WebDavStorageProvider : IStorageProvider { 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); - var response = await _httpClient.SendAsync(headRequest); + var response = await client.SendAsync(headRequest); if (response.IsSuccessStatusCode) return; // 创建目录 - 使用新的请求对象 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 || @@ -174,13 +182,13 @@ public class WebDavStorageProvider : IStorageProvider await EnsureDirectoryExistsAsync(parentPath); 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) { using var putRequest = new HttpRequestMessage(HttpMethod.Put, $"{requestUri}/.dummy"); putRequest.Content = new StringContent(string.Empty); - await _httpClient.SendAsync(putRequest); + await client.SendAsync(putRequest); } else { @@ -192,7 +200,7 @@ public class WebDavStorageProvider : IStorageProvider { using var putRequest = new HttpRequestMessage(HttpMethod.Put, $"{requestUri}/.dummy"); putRequest.Content = new StringContent(string.Empty); - var putResponse = await _httpClient.SendAsync(putRequest); + var putResponse = await client.SendAsync(putRequest); putResponse.EnsureSuccessStatusCode(); } else diff --git a/View/src/pages/settings/SystemConfig.tsx b/View/src/pages/settings/SystemConfig.tsx index 932e1dd..74a9b25 100644 --- a/View/src/pages/settings/SystemConfig.tsx +++ b/View/src/pages/settings/SystemConfig.tsx @@ -137,21 +137,43 @@ const SystemConfig: React.FC = () => { /> - - + + + + + + + handleSaveConfig('Authentication', key, value)} + descriptions={{ + "GitHubClientId": 'GitHub OAuth 应用客户端ID', + "GitHubClientSecret": 'GitHub OAuth 应用客户端密钥', + "GitHubCallbackUrl": 'GitHub OAuth 认证回调地址' + }} + isMobile={isMobile} + /> + + @@ -167,23 +189,6 @@ const SystemConfig: React.FC = () => { /> - - handleSaveConfig('Authentication', key, value)} - descriptions={{ - "GitHubClientId": 'GitHub OAuth 应用客户端ID', - "GitHubClientSecret": 'GitHub OAuth 应用客户端密钥', - "GitHubCallbackUrl": 'GitHub OAuth 认证回调地址' - }} - /> - -