diff --git a/Extensions/ApiExtensions.cs b/Extensions/ApiExtensions.cs new file mode 100644 index 0000000..0c3ce6b --- /dev/null +++ b/Extensions/ApiExtensions.cs @@ -0,0 +1,28 @@ +using Scalar.AspNetCore; + +namespace Foxel.Extensions; + +/// +/// API相关的扩展方法 +/// +public static class ApiExtensions +{ + /// + /// 添加应用程序OpenAPI + /// + public static IServiceCollection AddApplicationOpenApi(this IServiceCollection services) + { + services.AddOpenApi(opt => { opt.AddDocumentTransformer(); }); + return services; + } + + /// + /// 使用应用程序OpenAPI + /// + public static WebApplication UseApplicationOpenApi(this WebApplication app) + { + app.MapOpenApi(); + app.MapScalarApiReference(); + return app; + } +} diff --git a/Extensions/ApplicationBuilderExtensions.cs b/Extensions/ApplicationBuilderExtensions.cs deleted file mode 100644 index 841fe38..0000000 --- a/Extensions/ApplicationBuilderExtensions.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Microsoft.Extensions.FileProviders; -using Scalar.AspNetCore; - -namespace Foxel.Extensions; - -public static class ApplicationBuilderExtensions -{ - public static void UseApplicationStaticFiles(this WebApplication app) - { - var uploadsPath = Path.Combine(Directory.GetCurrentDirectory(), "Uploads"); - if (!Directory.Exists(uploadsPath)) - { - Directory.CreateDirectory(uploadsPath); - } - - app.UseStaticFiles(new StaticFileOptions - { - FileProvider = new PhysicalFileProvider(uploadsPath), - RequestPath = "/Uploads" - }); - } - - public static void UseApplicationOpenApi(this WebApplication app) - { - app.MapOpenApi(); - app.MapScalarApiReference(); - } -} \ No newline at end of file diff --git a/Extensions/ApplicationConfigurationExtensions.cs b/Extensions/ApplicationConfigurationExtensions.cs new file mode 100644 index 0000000..bd5a87e --- /dev/null +++ b/Extensions/ApplicationConfigurationExtensions.cs @@ -0,0 +1,56 @@ +using Foxel.Services.Initializer; + +namespace Foxel.Extensions; + +/// +/// 应用程序配置扩展方法 +/// +public static class ApplicationConfigurationExtensions +{ + /// + /// 配置应用程序中间件管道 + /// + public static WebApplication ConfigureApplicationPipeline(this WebApplication app) + { + // 转发头处理 + app.UseForwardedHeaders(); + + // 静态文件 + app.UseApplicationStaticFiles(); + + // 开发环境特定配置 + if (!app.Environment.IsDevelopment()) + { + app.UseExceptionHandler("/Error", createScopeForErrors: true); + app.UseHsts(); + } + + // API文档 + app.UseApplicationOpenApi(); + + // CORS + app.UseCors("MyAllowSpecificOrigins"); + + // 身份验证和授权 + app.UseAuthentication(); + app.UseAuthorization(); + + // 路由 + app.MapControllers(); + app.UseHttpsRedirection(); + + return app; + } + + /// + /// 初始化应用程序 + /// + public static async Task InitializeApplicationAsync(this WebApplication app) + { + using var scope = app.Services.CreateScope(); + var initializer = scope.ServiceProvider.GetRequiredService(); + await initializer.InitializeAsync(); + + return app; + } +} diff --git a/Extensions/ApplicationServiceExtensions.cs b/Extensions/ApplicationServiceExtensions.cs new file mode 100644 index 0000000..f52b7fa --- /dev/null +++ b/Extensions/ApplicationServiceExtensions.cs @@ -0,0 +1,40 @@ +namespace Foxel.Extensions; + +/// +/// 应用程序服务配置扩展方法 +/// +public static class ApplicationServiceExtensions +{ + /// + /// 添加所有应用程序服务 + /// + public static IServiceCollection AddApplicationServices(this IServiceCollection services, IConfiguration configuration) + { + // 基础服务 + services.AddControllers(); + services.AddHttpServices(); + + // 数据库 + services.AddApplicationDbContext(configuration); + + // 核心业务服务 + services.AddCoreServices(); + services.AddManagementServices(); + services.AddBackgroundServices(); + services.AddAiServices(); + services.AddInitializationServices(); + + // 身份验证和授权 + services.AddApplicationAuthentication(); + services.AddApplicationAuthorization(); + services.AddApplicationCors(); + + // API文档 + services.AddApplicationOpenApi(); + + // HTTP相关 + services.AddForwardedHeaders(); + + return services; + } +} diff --git a/Extensions/AuthenticationExtensions.cs b/Extensions/AuthenticationExtensions.cs new file mode 100644 index 0000000..3ab7dc7 --- /dev/null +++ b/Extensions/AuthenticationExtensions.cs @@ -0,0 +1,75 @@ +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.IdentityModel.Tokens; +using System.Text; +using Foxel.Services.Configuration; + +namespace Foxel.Extensions; + +/// +/// 身份验证和授权相关的扩展方法 +/// +public static class AuthenticationExtensions +{ + /// + /// 添加应用程序身份验证 + /// + public static IServiceCollection AddApplicationAuthentication(this IServiceCollection services) + { + // 构建临时服务提供者来获取配置服务 + using var serviceProvider = services.BuildServiceProvider(); + var configuration = serviceProvider.GetRequiredService(); + + services.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; + }) + .AddJwtBearer(options => + { + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidateAudience = true, + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + ValidIssuer = configuration["Jwt:Issuer"], + ValidAudience = configuration["Jwt:Audience"], + IssuerSigningKey = new SymmetricSecurityKey( + Encoding.UTF8.GetBytes(configuration["Jwt:SecretKey"])) + }; + }); + + return services; + } + + /// + /// 添加应用程序授权 + /// + public static IServiceCollection AddApplicationAuthorization(this IServiceCollection services) + { + services.AddAuthorization(options => + { + options.DefaultPolicy = new Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .Build(); + }); + + return services; + } + + /// + /// 添加应用程序CORS配置 + /// + public static IServiceCollection AddApplicationCors(this IServiceCollection services) + { + services.AddCors(options => + { + options.AddPolicy(name: "MyAllowSpecificOrigins", + policy => { policy.WithOrigins().AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod(); }); + }); + + return services; + } +} diff --git a/Extensions/CoreServiceExtensions.cs b/Extensions/CoreServiceExtensions.cs new file mode 100644 index 0000000..f1a70b7 --- /dev/null +++ b/Extensions/CoreServiceExtensions.cs @@ -0,0 +1,94 @@ +using Foxel.Services; +using Foxel.Services.AI; +using Foxel.Services.Auth; +using Foxel.Services.Background; +using Foxel.Services.Background.Processors; +using Foxel.Services.Configuration; +using Foxel.Services.Initializer; +using Foxel.Services.Management; +using Foxel.Services.Mapping; +using Foxel.Services.Media; +using Foxel.Services.Storage; +using Foxel.Services.VectorDb; + +namespace Foxel.Extensions; + +/// +/// 核心业务服务相关的扩展方法 +/// +public static class CoreServiceExtensions +{ + /// + /// 添加核心业务服务 + /// + public static IServiceCollection AddCoreServices(this IServiceCollection services) + { + // 配置服务 + services.AddSingleton(); + + // 核心业务服务 + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + return services; + } + + /// + /// 添加管理服务 + /// + public static IServiceCollection AddManagementServices(this IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + return services; + } + + /// + /// 添加后台任务服务 + /// + public static IServiceCollection AddBackgroundServices(this IServiceCollection services) + { + services.AddSingleton(); + services.AddHostedService(); + + // 任务处理器 + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + return services; + } + + /// + /// 添加AI和向量数据库服务 + /// + public static IServiceCollection AddAiServices(this IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(provider => + provider.GetRequiredService()); + services.AddHostedService(); + + return services; + } + + /// + /// 添加初始化服务 + /// + public static IServiceCollection AddInitializationServices(this IServiceCollection services) + { + services.AddSingleton(); + return services; + } +} diff --git a/Extensions/DatabaseExtensions.cs b/Extensions/DatabaseExtensions.cs new file mode 100644 index 0000000..3cdf113 --- /dev/null +++ b/Extensions/DatabaseExtensions.cs @@ -0,0 +1,27 @@ +using Microsoft.EntityFrameworkCore; + +namespace Foxel.Extensions; + +/// +/// 数据库相关的扩展方法 +/// +public static class DatabaseExtensions +{ + /// + /// 添加应用程序数据库上下文 + /// + public static IServiceCollection AddApplicationDbContext(this IServiceCollection services, IConfiguration configuration) + { + var connectionString = configuration.GetConnectionString("DefaultConnection"); + if (string.IsNullOrEmpty(connectionString)) + { + connectionString = Environment.GetEnvironmentVariable("DEFAULT_CONNECTION"); + } + + Console.WriteLine($"数据库连接: {connectionString}"); + services.AddDbContextFactory(options => + options.UseNpgsql(connectionString)); + + return services; + } +} diff --git a/Extensions/HttpExtensions.cs b/Extensions/HttpExtensions.cs new file mode 100644 index 0000000..3327fa9 --- /dev/null +++ b/Extensions/HttpExtensions.cs @@ -0,0 +1,56 @@ +using Microsoft.AspNetCore.HttpOverrides; +using Microsoft.Extensions.FileProviders; + +namespace Foxel.Extensions; + +/// +/// HTTP相关的扩展方法 +/// +public static class HttpExtensions +{ + /// + /// 添加HTTP相关服务 + /// + public static IServiceCollection AddHttpServices(this IServiceCollection services) + { + services.AddHttpClient(); + services.AddHttpContextAccessor(); + services.AddMemoryCache(); + return services; + } + + /// + /// 配置转发头信息 + /// + public static IServiceCollection AddForwardedHeaders(this IServiceCollection services) + { + services.Configure(options => + { + options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; + options.KnownNetworks.Clear(); + options.KnownProxies.Clear(); + }); + + return services; + } + + /// + /// 使用应用程序静态文件 + /// + public static WebApplication UseApplicationStaticFiles(this WebApplication app) + { + var uploadsPath = Path.Combine(Directory.GetCurrentDirectory(), "Uploads"); + if (!Directory.Exists(uploadsPath)) + { + Directory.CreateDirectory(uploadsPath); + } + + app.UseStaticFiles(new StaticFileOptions + { + FileProvider = new PhysicalFileProvider(uploadsPath), + RequestPath = "/Uploads" + }); + + return app; + } +} diff --git a/Extensions/ServiceCollectionExtensions.cs b/Extensions/ServiceCollectionExtensions.cs deleted file mode 100644 index a841026..0000000 --- a/Extensions/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,117 +0,0 @@ -using Foxel.Services; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.EntityFrameworkCore; -using Microsoft.IdentityModel.Tokens; -using System.Text; -using Foxel.Services.AI; -using Foxel.Services.Auth; -using Foxel.Services.Background; -using Foxel.Services.Configuration; -using Foxel.Services.Initializer; -using Foxel.Services.Management; -using Foxel.Services.Media; -using Foxel.Services.Storage; -using Foxel.Services.Background.Processors; -using Foxel.Services.Mapping; -using Foxel.Services.VectorDb; - -namespace Foxel.Extensions; - -public static class ServiceCollectionExtensions -{ - public static void AddCoreServices(this IServiceCollection services) - { - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddHostedService(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - - } - - public static void AddApplicationDbContext(this IServiceCollection services, IConfiguration configuration) - { - var connectionString = configuration.GetConnectionString("DefaultConnection"); - if (string.IsNullOrEmpty(connectionString)) - { - connectionString = Environment.GetEnvironmentVariable("DEFAULT_CONNECTION"); - } - - Console.WriteLine($"数据库连接: {connectionString}"); - services.AddDbContextFactory(options => - options.UseNpgsql(connectionString)); - } - - public static void AddApplicationOpenApi(this IServiceCollection services) - { - services.AddOpenApi(opt => { opt.AddDocumentTransformer(); }); - } - - public static void AddApplicationAuthentication(this IServiceCollection services) - { - IConfigService configuration = services.BuildServiceProvider().GetRequiredService(); - services.AddAuthentication(options => - { - options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; - options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; - }) - .AddJwtBearer(options => - { - options.TokenValidationParameters = new TokenValidationParameters - { - ValidateIssuer = true, - ValidateAudience = true, - ValidateLifetime = true, - ValidateIssuerSigningKey = true, - ValidIssuer = configuration["Jwt:Issuer"], - ValidAudience = configuration["Jwt:Audience"], - IssuerSigningKey = new SymmetricSecurityKey( - Encoding.UTF8.GetBytes(configuration["Jwt:SecretKey"])) - }; - }); - } - - public static void AddApplicationAuthorization(this IServiceCollection services) - { - services.AddAuthorization(options => - { - options.DefaultPolicy = new Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder() - .RequireAuthenticatedUser() - .Build(); - }); - } - - public static void AddApplicationCors(this IServiceCollection services) - { - services.AddCors(options => - { - options.AddPolicy(name: "MyAllowSpecificOrigins", - policy => { policy.WithOrigins().AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod(); }); - }); - } - - public static void AddVectorDbServices(this IServiceCollection services) - { - services.AddSingleton(); - services.AddSingleton(provider => - provider.GetRequiredService()); - } -} \ No newline at end of file diff --git a/Models/BatchDeleteResult.cs b/Models/BatchDeleteResult.cs new file mode 100644 index 0000000..ef87a2a --- /dev/null +++ b/Models/BatchDeleteResult.cs @@ -0,0 +1,8 @@ +namespace Foxel.Models; + +public class BatchDeleteResult +{ + public int SuccessCount { get; set; } + public int FailedCount { get; set; } + public List FailedIds { get; set; } = new(); +} \ No newline at end of file diff --git a/Program.cs b/Program.cs index 59b16ef..23a1207 100644 --- a/Program.cs +++ b/Program.cs @@ -1,52 +1,27 @@ using Foxel.Extensions; -using Foxel.Services.Initializer; -using Foxel.Services.VectorDb; -using Microsoft.AspNetCore.HttpOverrides; var builder = WebApplication.CreateBuilder(args); var environment = builder.Environment; + Console.WriteLine($"当前环境: {environment.EnvironmentName}"); + +// 配置日志记录 builder.Logging.AddDatabaseLogging(config => { config.MinLevel = LogLevel.Information; config.Enabled = true; }); -builder.Services.AddMemoryCache(); -builder.Services.AddApplicationDbContext(builder.Configuration); -builder.Services.AddApplicationOpenApi(); -builder.Services.AddControllers(); -builder.Services.AddHttpClient(); -builder.Services.AddCoreServices(); -builder.Services.AddHttpContextAccessor(); -builder.Services.AddApplicationAuthentication(); -builder.Services.AddApplicationAuthorization(); -builder.Services.AddApplicationCors(); -builder.Services.AddVectorDbServices(); -builder.Services.AddHostedService(); -builder.Services.Configure(options => -{ - options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; - options.KnownNetworks.Clear(); - options.KnownProxies.Clear(); -}); + +// 添加所有应用程序服务 +builder.Services.AddApplicationServices(builder.Configuration); + var app = builder.Build(); -using (var scope = app.Services.CreateScope()) -{ - var initializer = scope.ServiceProvider.GetRequiredService(); - await initializer.InitializeAsync(); -} -app.UseForwardedHeaders(); -app.UseApplicationStaticFiles(); -if (!app.Environment.IsDevelopment()) -{ - app.UseExceptionHandler("/Error", createScopeForErrors: true); - app.UseHsts(); -} -app.UseApplicationOpenApi(); -app.UseCors("MyAllowSpecificOrigins"); -app.UseAuthentication(); -app.UseAuthorization(); -app.MapControllers(); -app.UseHttpsRedirection(); +// 初始化应用程序 +await app.InitializeApplicationAsync(); + +// 配置中间件管道 +app.ConfigureApplicationPipeline(); + +// 启动应用程序 app.Run(); \ No newline at end of file diff --git a/Services/AI/FaceClusteringService.cs b/Services/Face/FaceClusteringService.cs similarity index 100% rename from Services/AI/FaceClusteringService.cs rename to Services/Face/FaceClusteringService.cs diff --git a/Services/AI/IFaceClusteringService.cs b/Services/Face/IFaceClusteringService.cs similarity index 100% rename from Services/AI/IFaceClusteringService.cs rename to Services/Face/IFaceClusteringService.cs diff --git a/Services/Management/IUserManagementService.cs b/Services/Management/IUserManagementService.cs index d78ac6e..c33b51c 100644 --- a/Services/Management/IUserManagementService.cs +++ b/Services/Management/IUserManagementService.cs @@ -12,11 +12,4 @@ public interface IUserManagementService Task UpdateUserAsync(int id, string userName, string email, string role); Task DeleteUserAsync(int id); Task BatchDeleteUsersAsync(List ids); -} - -public class BatchDeleteResult -{ - public int SuccessCount { get; set; } - public int FailedCount { get; set; } - public List FailedIds { get; set; } = new(); -} +} \ No newline at end of file