Refactor face clustering service and related interfaces

This commit is contained in:
shiyu
2025-06-23 13:01:21 +08:00
parent ec2ff921f9
commit 3d3ecac4ce
14 changed files with 399 additions and 192 deletions

View File

@@ -0,0 +1,28 @@
using Scalar.AspNetCore;
namespace Foxel.Extensions;
/// <summary>
/// API相关的扩展方法
/// </summary>
public static class ApiExtensions
{
/// <summary>
/// 添加应用程序OpenAPI
/// </summary>
public static IServiceCollection AddApplicationOpenApi(this IServiceCollection services)
{
services.AddOpenApi(opt => { opt.AddDocumentTransformer<BearerSecuritySchemeTransformer>(); });
return services;
}
/// <summary>
/// 使用应用程序OpenAPI
/// </summary>
public static WebApplication UseApplicationOpenApi(this WebApplication app)
{
app.MapOpenApi();
app.MapScalarApiReference();
return app;
}
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1,56 @@
using Foxel.Services.Initializer;
namespace Foxel.Extensions;
/// <summary>
/// 应用程序配置扩展方法
/// </summary>
public static class ApplicationConfigurationExtensions
{
/// <summary>
/// 配置应用程序中间件管道
/// </summary>
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;
}
/// <summary>
/// 初始化应用程序
/// </summary>
public static async Task<WebApplication> InitializeApplicationAsync(this WebApplication app)
{
using var scope = app.Services.CreateScope();
var initializer = scope.ServiceProvider.GetRequiredService<IDatabaseInitializer>();
await initializer.InitializeAsync();
return app;
}
}

View File

@@ -0,0 +1,40 @@
namespace Foxel.Extensions;
/// <summary>
/// 应用程序服务配置扩展方法
/// </summary>
public static class ApplicationServiceExtensions
{
/// <summary>
/// 添加所有应用程序服务
/// </summary>
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;
}
}

View File

@@ -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;
/// <summary>
/// 身份验证和授权相关的扩展方法
/// </summary>
public static class AuthenticationExtensions
{
/// <summary>
/// 添加应用程序身份验证
/// </summary>
public static IServiceCollection AddApplicationAuthentication(this IServiceCollection services)
{
// 构建临时服务提供者来获取配置服务
using var serviceProvider = services.BuildServiceProvider();
var configuration = serviceProvider.GetRequiredService<IConfigService>();
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;
}
/// <summary>
/// 添加应用程序授权
/// </summary>
public static IServiceCollection AddApplicationAuthorization(this IServiceCollection services)
{
services.AddAuthorization(options =>
{
options.DefaultPolicy = new Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
return services;
}
/// <summary>
/// 添加应用程序CORS配置
/// </summary>
public static IServiceCollection AddApplicationCors(this IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy(name: "MyAllowSpecificOrigins",
policy => { policy.WithOrigins().AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod(); });
});
return services;
}
}

View File

@@ -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;
/// <summary>
/// 核心业务服务相关的扩展方法
/// </summary>
public static class CoreServiceExtensions
{
/// <summary>
/// 添加核心业务服务
/// </summary>
public static IServiceCollection AddCoreServices(this IServiceCollection services)
{
// 配置服务
services.AddSingleton<IConfigService, ConfigService>();
// 核心业务服务
services.AddSingleton<IAiService, AiService>();
services.AddSingleton<IPictureService, PictureService>();
services.AddSingleton<IAuthService, AuthService>();
services.AddSingleton<ITagService, TagService>();
services.AddSingleton<IAlbumService, AlbumService>();
services.AddSingleton<IStorageService, StorageService>();
services.AddSingleton<IMappingService, MappingService>();
return services;
}
/// <summary>
/// 添加管理服务
/// </summary>
public static IServiceCollection AddManagementServices(this IServiceCollection services)
{
services.AddSingleton<IUserManagementService, UserManagementService>();
services.AddSingleton<IPictureManagementService, PictureManagementService>();
services.AddSingleton<IAlbumManagementService, AlbumManagementService>();
services.AddSingleton<ILogManagementService, LogManagementService>();
services.AddSingleton<IStorageManagementService, StorageManagementService>();
services.AddSingleton<IFaceManagementService, FaceManagementService>();
return services;
}
/// <summary>
/// 添加后台任务服务
/// </summary>
public static IServiceCollection AddBackgroundServices(this IServiceCollection services)
{
services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();
services.AddHostedService<QueuedHostedService>();
// 任务处理器
services.AddSingleton<PictureTaskProcessor>();
services.AddSingleton<FaceRecognitionTaskProcessor>();
services.AddSingleton<VisualRecognitionTaskProcessor>();
return services;
}
/// <summary>
/// 添加AI和向量数据库服务
/// </summary>
public static IServiceCollection AddAiServices(this IServiceCollection services)
{
services.AddSingleton<IFaceClusteringService, FaceClusteringService>();
services.AddSingleton<VectorDbManager>();
services.AddSingleton<IVectorDbService>(provider =>
provider.GetRequiredService<VectorDbManager>());
services.AddHostedService<VectorDbInitializer>();
return services;
}
/// <summary>
/// 添加初始化服务
/// </summary>
public static IServiceCollection AddInitializationServices(this IServiceCollection services)
{
services.AddSingleton<IDatabaseInitializer, DatabaseInitializer>();
return services;
}
}

View File

@@ -0,0 +1,27 @@
using Microsoft.EntityFrameworkCore;
namespace Foxel.Extensions;
/// <summary>
/// 数据库相关的扩展方法
/// </summary>
public static class DatabaseExtensions
{
/// <summary>
/// 添加应用程序数据库上下文
/// </summary>
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<MyDbContext>(options =>
options.UseNpgsql(connectionString));
return services;
}
}

View File

@@ -0,0 +1,56 @@
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Extensions.FileProviders;
namespace Foxel.Extensions;
/// <summary>
/// HTTP相关的扩展方法
/// </summary>
public static class HttpExtensions
{
/// <summary>
/// 添加HTTP相关服务
/// </summary>
public static IServiceCollection AddHttpServices(this IServiceCollection services)
{
services.AddHttpClient();
services.AddHttpContextAccessor();
services.AddMemoryCache();
return services;
}
/// <summary>
/// 配置转发头信息
/// </summary>
public static IServiceCollection AddForwardedHeaders(this IServiceCollection services)
{
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
});
return services;
}
/// <summary>
/// 使用应用程序静态文件
/// </summary>
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;
}
}

View File

@@ -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<IConfigService, ConfigService>();
services.AddSingleton<IAiService, AiService>();
services.AddSingleton<IPictureService, PictureService>();
services.AddSingleton<IAuthService, AuthService>();
services.AddSingleton<ITagService, TagService>();
services.AddSingleton<IAlbumService, AlbumService>();
services.AddSingleton<IUserManagementService, UserManagementService>();
services.AddSingleton<IPictureManagementService, PictureManagementService>();
services.AddSingleton<IAlbumManagementService, AlbumManagementService>();
services.AddSingleton<ILogManagementService, LogManagementService>();
services.AddSingleton<IStorageManagementService, StorageManagementService>();
services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IStorageService, StorageService>();
services.AddSingleton<PictureTaskProcessor>();
services.AddSingleton<FaceRecognitionTaskProcessor>();
services.AddSingleton<VisualRecognitionTaskProcessor>();
services.AddSingleton<IDatabaseInitializer, DatabaseInitializer>();
services.AddSingleton<IMappingService, MappingService>();
services.AddSingleton<IFaceManagementService, FaceManagementService>();
services.AddSingleton<IFaceClusteringService, FaceClusteringService>();
}
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<MyDbContext>(options =>
options.UseNpgsql(connectionString));
}
public static void AddApplicationOpenApi(this IServiceCollection services)
{
services.AddOpenApi(opt => { opt.AddDocumentTransformer<BearerSecuritySchemeTransformer>(); });
}
public static void AddApplicationAuthentication(this IServiceCollection services)
{
IConfigService configuration = services.BuildServiceProvider().GetRequiredService<IConfigService>();
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<VectorDbManager>();
services.AddSingleton<IVectorDbService>(provider =>
provider.GetRequiredService<VectorDbManager>());
}
}

View File

@@ -0,0 +1,8 @@
namespace Foxel.Models;
public class BatchDeleteResult
{
public int SuccessCount { get; set; }
public int FailedCount { get; set; }
public List<int> FailedIds { get; set; } = new();
}

View File

@@ -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<VectorDbInitializer>();
builder.Services.Configure<ForwardedHeadersOptions>(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<IDatabaseInitializer>();
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();

View File

@@ -12,11 +12,4 @@ public interface IUserManagementService
Task<UserResponse> UpdateUserAsync(int id, string userName, string email, string role);
Task<bool> DeleteUserAsync(int id);
Task<BatchDeleteResult> BatchDeleteUsersAsync(List<int> ids);
}
public class BatchDeleteResult
{
public int SuccessCount { get; set; }
public int FailedCount { get; set; }
public List<int> FailedIds { get; set; } = new();
}
}