From 853efaa2fe64341adf53dc901fb2e5e2186735c0 Mon Sep 17 00:00:00 2001 From: shiyu Date: Tue, 10 Jun 2025 16:42:39 +0800 Subject: [PATCH] feat(auth): add configuration options for user registration and anonymous image hosting --- Api/AuthController.cs | 9 +- Api/PictureController.cs | 13 +- Services/Initializer/DatabaseInitializer.cs | 2 + Web/src/pages/admin/system/ConfigFormItem.tsx | 34 +++- Web/src/pages/admin/system/ConfigTabs.tsx | 167 ++++++++++-------- Web/src/pages/admin/system/Index.tsx | 34 +++- 6 files changed, 167 insertions(+), 92 deletions(-) diff --git a/Api/AuthController.cs b/Api/AuthController.cs index 98c2be7..1fb67f3 100644 --- a/Api/AuthController.cs +++ b/Api/AuthController.cs @@ -9,7 +9,7 @@ using Foxel.Services.Configuration; namespace Foxel.Controllers; [Route("api/auth")] -public class AuthController(IAuthService authService) : BaseApiController +public class AuthController(IAuthService authService, IConfigService configuration) : BaseApiController { [HttpPost("register")] public async Task>> Register([FromBody] RegisterRequest request) @@ -19,6 +19,13 @@ public class AuthController(IAuthService authService) : BaseApiController return Error("请求数据无效"); } + // 检查是否允许新用户注册 + var enableRegistration = configuration["AppSettings:EnableRegistration"]; + if (string.Equals(enableRegistration, "false", StringComparison.OrdinalIgnoreCase)) + { + return Error("新用户注册功能已关闭"); + } + var (success, message, user) = await authService.RegisterUserAsync(request); if (!success) { diff --git a/Api/PictureController.cs b/Api/PictureController.cs index 954cab7..691c2d9 100644 --- a/Api/PictureController.cs +++ b/Api/PictureController.cs @@ -9,12 +9,13 @@ using Foxel.Services.Storage; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using System.Text.Json; // Added for JsonSerializer in GetTelegramFile if it were kept +using Foxel.Services.Configuration; // Added for IConfigService namespace Foxel.Api; [Authorize] [Route("api/picture")] -public class PictureController(IPictureService pictureService, IStorageService storageService, ILogger logger) : BaseApiController +public class PictureController(IPictureService pictureService, IStorageService storageService, ILogger logger, IConfigService configuration) : BaseApiController { private readonly ILogger _logger = logger; @@ -74,6 +75,16 @@ public class PictureController(IPictureService pictureService, IStorageService s try { var userId = GetCurrentUserId(); + + if (userId == null) + { + var enableAnonymousUpload = configuration["AppSettings:EnableAnonymousImageHosting"]; + if (string.Equals(enableAnonymousUpload, "false", StringComparison.OrdinalIgnoreCase)) + { + return Error("匿名上传功能已关闭,请登录后操作", 403); + } + } + await using var stream = request.File.OpenReadStream(); var result = await pictureService.UploadPictureAsync( request.File.FileName, diff --git a/Services/Initializer/DatabaseInitializer.cs b/Services/Initializer/DatabaseInitializer.cs index d192fee..da271b7 100644 --- a/Services/Initializer/DatabaseInitializer.cs +++ b/Services/Initializer/DatabaseInitializer.cs @@ -73,6 +73,8 @@ public class DatabaseInitializer( // 其他配置 ["Storage:DefaultStorageModeId"] = "1", ["AppSettings:ServerUrl"] = "", + ["AppSettings:EnableRegistration"] = "true", + ["AppSettings:EnableAnonymousImageHosting"] = "true", ["VectorDb:Type"] = "InMemory" }; diff --git a/Web/src/pages/admin/system/ConfigFormItem.tsx b/Web/src/pages/admin/system/ConfigFormItem.tsx index 4d995a2..4404375 100644 --- a/Web/src/pages/admin/system/ConfigFormItem.tsx +++ b/Web/src/pages/admin/system/ConfigFormItem.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Row, Col, Form, Input, Button, Space, Tooltip } from 'antd'; +import { Row, Col, Form, Input, Button, Space, Tooltip, Switch } from 'antd'; import { LockOutlined, QuestionCircleOutlined, SaveOutlined } from '@ant-design/icons'; interface ConfigFormItemProps { @@ -23,6 +23,9 @@ const ConfigFormItem: React.FC = ({ isMobile, onSave, }) => { + const booleanAppSettings = ['EnableRegistration', 'EnableAnonymousImageHosting']; + const isBooleanAppSetting = groupName === 'AppSettings' && booleanAppSettings.includes(itemKey); + return ( @@ -31,7 +34,7 @@ const ConfigFormItem: React.FC = ({ label={ {itemKey} - {isSecret && } + {isSecret && !isBooleanAppSetting && } {description && ( @@ -39,14 +42,29 @@ const ConfigFormItem: React.FC = ({ )} } - initialValue={isSecret ? '' : currentValue} - rules={isSecret ? [] : []} + initialValue={ + isBooleanAppSetting + ? currentValue === 'true' + : isSecret + ? '' + : currentValue + } + valuePropName={isBooleanAppSetting ? 'checked' : undefined} + rules={isSecret || isBooleanAppSetting ? [] : []} style={{ marginBottom: isMobile ? 8 : 16 }} - help={isSecret && currentValue ? - 当前已设置值。如需修改,请输入新值。 : - (isSecret ? 此为私密字段。 : null)} + help={ + isBooleanAppSetting + ? null + : isSecret && currentValue + ? 当前已设置值。如需修改,请输入新值。 + : isSecret + ? 此为私密字段。 + : null + } > - {isSecret ? ( + {isBooleanAppSetting ? ( + + ) : isSecret ? ( = ({ isMobile={isMobile} >
- {renderConfigFormItems(formsMap.AppSettings, "AppSettings", ['ServerUrl', 'MaxConcurrentTasks'])} + {renderConfigFormItems(formsMap.AppSettings, "AppSettings", ['ServerUrl', 'MaxConcurrentTasks', 'EnableRegistration', 'EnableAnonymousImageHosting'])} + + ) diff --git a/Web/src/pages/admin/system/Index.tsx b/Web/src/pages/admin/system/Index.tsx index c14715c..0afa81c 100644 --- a/Web/src/pages/admin/system/Index.tsx +++ b/Web/src/pages/admin/system/Index.tsx @@ -43,7 +43,9 @@ const allDescriptions: Record> = { }, AppSettings: { ServerUrl: '服务器URL', - MaxConcurrentTasks: '后台任务最大并发处理数量 (例如: 图像分析、标签生成等)' + MaxConcurrentTasks: '后台任务最大并发处理数量 (例如: 图像分析、标签生成等)', + EnableRegistration: '是否允许新用户注册 (true/false)', + EnableAnonymousImageHosting: '是否允许匿名用户上传图片 (true/false)' }, Upload: { HighQualityImageCompressionQuality: '高清图片的压缩质量,越高图片质量越好但文件越大。范围 50-100。', @@ -52,6 +54,7 @@ const allDescriptions: Record> = { } }; +const booleanAppSettings = ['EnableRegistration', 'EnableAnonymousImageHosting']; const System: React.FC = () => { const isMobile = useIsMobile(); @@ -115,9 +118,13 @@ const System: React.FC = () => { const formInstance = formsMap[formInstanceKey]; if (formInstance) { - const initialGroupValues: Record = {}; + const initialGroupValues: Record = {}; // Changed to any for boolean values Object.keys(configGroups[group]).forEach(key => { - if (!secretFieldsMap[group]?.includes(key)) { + if (group === 'AppSettings' && booleanAppSettings.includes(key)) { + initialGroupValues[key] = configGroups[group][key] === 'true'; + } else if (group === 'Upload' && ['ThumbnailMaxWidth', 'ThumbnailCompressionQuality', 'HighQualityImageCompressionQuality'].includes(key)) { + initialGroupValues[key] = parseInt(configGroups[group][key] || (key === 'ThumbnailMaxWidth' ? '400' : (key === 'ThumbnailCompressionQuality' ? '75' : '95')), 10); + } else if (!secretFieldsMap[group]?.includes(key)) { initialGroupValues[key] = configGroups[group][key]; } else { initialGroupValues[key] = ''; @@ -211,9 +218,13 @@ const System: React.FC = () => { const handleSaveSingleConfig = async (formInstance: any, groupName: string, key: string) => { try { await formInstance.validateFields([key]); - const value = formInstance.getFieldValue(key); + let value = formInstance.getFieldValue(key); const isSecret = secretFields[groupName]?.includes(key); + if (groupName === 'AppSettings' && booleanAppSettings.includes(key) && typeof value === 'boolean') { + value = String(value); + } + if (isSecret && (value === '' || value === undefined)) { message.info(`未输入 ${key} 的新值,不作更改。`); return; @@ -242,7 +253,12 @@ const System: React.FC = () => { // 计算需要保存的总数 for (const key of itemKeys) { - const value = values[key]; + let value = values[key]; + if (groupName === 'AppSettings' && booleanAppSettings.includes(key) && typeof value === 'boolean') { + value = String(value); + } else if (groupName === 'Upload' && typeof value === 'number') { // Ensure numbers are converted to strings for saving + value = String(value); + } const isSecret = secretFields[groupName]?.includes(key); if (!(isSecret && (value === '' || value === undefined)) && (isSecret || configs[groupName]?.[key] !== value)) { @@ -265,9 +281,15 @@ const System: React.FC = () => { }); for (const key of itemKeys) { - const value = values[key]; + let value = values[key]; const isSecret = secretFields[groupName]?.includes(key); + if (groupName === 'AppSettings' && booleanAppSettings.includes(key) && typeof value === 'boolean') { + value = String(value); + } else if (groupName === 'Upload' && typeof value === 'number') { // Ensure numbers are converted to strings for saving + value = String(value); + } + if (isSecret && (value === '' || value === undefined)) { continue; }