feat: 后端架构重构 — Endpoint→Service→Repository分层 + AI确认机制 + 异步任务持久化
- 核心业务拆分为 Endpoint → Application Service → Repository 三层 - AI写入操作必须用户确认后才写库(确认卡片机制) - 报告/饮食/用药分析改为持久化任务队列(原子领取/重试/重启恢复) - 运动计划修复: 连续真实日期替代周模板 - 用药提醒去重 + 通知Outbox预留 - 认证收拢到AuthService, 管理员收拢到AdminService - AI会话加用户归属校验防串号 - 提示词调整为患者视角 - 开发假数据已关闭 - 21/21测试通过, 0警告0错误
This commit is contained in:
@@ -1,13 +1,40 @@
|
||||
using System.Text;
|
||||
using Health.Application.AI;
|
||||
using Health.Application.Admin;
|
||||
using Health.Application.Auth;
|
||||
using Health.Application.Calendars;
|
||||
using Health.Application.Diets;
|
||||
using Health.Application.Exercises;
|
||||
using Health.Application.HealthArchives;
|
||||
using Health.Application.HealthRecords;
|
||||
using Health.Application.Medications;
|
||||
using Health.Application.Maintenance;
|
||||
using Health.Application.Notifications;
|
||||
using Health.Application.Reports;
|
||||
using Health.Application.Users;
|
||||
using Health.Infrastructure.AI;
|
||||
using Health.Infrastructure.Admin;
|
||||
using Health.Infrastructure.Auth;
|
||||
using Health.Infrastructure.Calendars;
|
||||
using Health.Infrastructure.Data;
|
||||
using Health.Infrastructure.Diets;
|
||||
using Health.Infrastructure.Exercises;
|
||||
using Health.Infrastructure.HealthArchives;
|
||||
using Health.Infrastructure.HealthRecords;
|
||||
using Health.Infrastructure.Medications;
|
||||
using Health.Infrastructure.Maintenance;
|
||||
using Health.Infrastructure.Notifications;
|
||||
using Health.Infrastructure.Reports;
|
||||
using Health.Infrastructure.Services;
|
||||
using Health.Infrastructure.Users;
|
||||
using Health.WebApi.BackgroundServices;
|
||||
using Health.WebApi.Converters;
|
||||
using Health.WebApi.Endpoints;
|
||||
using Health.WebApi.Middleware;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
// 加载 .env 文件(开发环境)
|
||||
@@ -28,6 +55,18 @@ if (File.Exists(envPath))
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
if (builder.Environment.IsDevelopment())
|
||||
{
|
||||
builder.Logging.ClearProviders();
|
||||
builder.Logging.AddConsole();
|
||||
builder.Logging.AddDebug();
|
||||
var dataProtectionPath = Path.Combine(Directory.GetCurrentDirectory(), "data-protection-keys");
|
||||
Directory.CreateDirectory(dataProtectionPath);
|
||||
builder.Services.AddDataProtection()
|
||||
.PersistKeysToFileSystem(new DirectoryInfo(dataProtectionPath))
|
||||
.SetApplicationName("HealthManager.Development");
|
||||
}
|
||||
|
||||
// ---- JSON 配置(枚举字符串、camelCase、UTC时间统一带Z)----
|
||||
builder.Services.ConfigureHttpJsonOptions(options =>
|
||||
{
|
||||
@@ -68,6 +107,44 @@ builder.Services.AddAuthorization();
|
||||
builder.Services.AddSingleton<JwtProvider>();
|
||||
builder.Services.AddSingleton<SmsService>();
|
||||
builder.Services.AddSingleton<PromptManager>();
|
||||
builder.Services.AddScoped<IAiWriteConfirmationStore, EfAiWriteConfirmationStore>();
|
||||
builder.Services.AddScoped<IAiToolExecutionService, AiToolExecutionService>();
|
||||
builder.Services.AddScoped<IAdminService, AdminService>();
|
||||
builder.Services.AddScoped<IAuthService, AuthService>();
|
||||
builder.Services.AddScoped<IAiConversationService, AiConversationService>();
|
||||
builder.Services.AddScoped<IAiConversationRepository, EfAiConversationRepository>();
|
||||
builder.Services.AddScoped<IPatientContextService, PatientContextService>();
|
||||
builder.Services.AddScoped<ICalendarService, CalendarService>();
|
||||
builder.Services.AddScoped<ICalendarRepository, EfCalendarRepository>();
|
||||
builder.Services.AddScoped<IDietService, DietService>();
|
||||
builder.Services.AddScoped<IDietRepository, EfDietRepository>();
|
||||
builder.Services.AddScoped<IDietImageAnalysisCoordinator, DietImageAnalysisCoordinator>();
|
||||
builder.Services.AddScoped<IDietImageAnalyzer, DietImageAnalyzer>();
|
||||
builder.Services.AddScoped<IDietImageAnalysisQueue, DietImageAnalysisQueue>();
|
||||
builder.Services.AddScoped<IExerciseService, ExerciseService>();
|
||||
builder.Services.AddScoped<IExerciseRepository, EfExerciseRepository>();
|
||||
builder.Services.AddScoped<IExerciseReminderProducer, ExerciseReminderProducer>();
|
||||
builder.Services.AddScoped<IHealthArchiveService, HealthArchiveService>();
|
||||
builder.Services.AddScoped<IHealthArchiveRepository, EfHealthArchiveRepository>();
|
||||
builder.Services.AddScoped<IHealthRecordService, HealthRecordService>();
|
||||
builder.Services.AddScoped<IHealthRecordRepository, EfHealthRecordRepository>();
|
||||
builder.Services.AddScoped<IMedicationService, MedicationService>();
|
||||
builder.Services.AddScoped<IMedicationRepository, EfMedicationRepository>();
|
||||
builder.Services.AddScoped<IMedicationReminderScanner, MedicationReminderScanner>();
|
||||
builder.Services.AddScoped<IMedicationReminderDispatcher, OutboxMedicationReminderDispatcher>();
|
||||
builder.Services.AddScoped<IMedicationReminderQueue, MedicationReminderQueue>();
|
||||
builder.Services.AddScoped<IMaintenanceService, MaintenanceService>();
|
||||
builder.Services.AddScoped<IMaintenanceRepository, EfMaintenanceRepository>();
|
||||
builder.Services.AddScoped<IInAppNotificationService, InAppNotificationService>();
|
||||
builder.Services.AddScoped<IInAppNotificationRepository, EfInAppNotificationRepository>();
|
||||
builder.Services.AddScoped<IReportService, ReportService>();
|
||||
builder.Services.AddScoped<IReportRepository, EfReportRepository>();
|
||||
builder.Services.AddScoped<IReportFileStorage, LocalReportFileStorage>();
|
||||
builder.Services.AddScoped<IReportAnalysisService, ReportAnalysisService>();
|
||||
builder.Services.AddScoped<IReportAnalysisQueue, EfReportAnalysisQueue>();
|
||||
builder.Services.AddScoped<IUserService, UserService>();
|
||||
builder.Services.AddScoped<IUserRepository, EfUserRepository>();
|
||||
builder.Services.AddScoped<DatabaseSchemaMigrator>();
|
||||
|
||||
// ---- AI 客户端(使用 IHttpClientFactory 区分 LLM 和 VLM)----
|
||||
builder.Services.AddHttpClient<DeepSeekClient>(client =>
|
||||
@@ -85,7 +162,11 @@ builder.Services.AddHttpClient<VisionClient>(client =>
|
||||
|
||||
// ---- 后台服务 ----
|
||||
builder.Services.AddHostedService<MedicationReminderService>();
|
||||
builder.Services.AddHostedService<ExerciseReminderService>();
|
||||
builder.Services.AddHostedService<MedicationReminderWorker>();
|
||||
builder.Services.AddHostedService<CleanupService>();
|
||||
builder.Services.AddHostedService<ReportAnalysisWorker>();
|
||||
builder.Services.AddHostedService<DietImageAnalysisWorker>();
|
||||
|
||||
// ---- OpenAPI ----
|
||||
builder.Services.AddOpenApi();
|
||||
@@ -110,6 +191,14 @@ app.UseCors();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
var uploadsPath = Path.Combine(Directory.GetCurrentDirectory(), "uploads");
|
||||
Directory.CreateDirectory(uploadsPath);
|
||||
app.UseStaticFiles(new StaticFileOptions
|
||||
{
|
||||
FileProvider = new PhysicalFileProvider(uploadsPath),
|
||||
RequestPath = "/uploads"
|
||||
});
|
||||
|
||||
if (app.Environment.IsDevelopment())
|
||||
app.MapOpenApi();
|
||||
|
||||
@@ -118,6 +207,7 @@ using (var scope = app.Services.CreateScope())
|
||||
{
|
||||
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
await db.Database.EnsureCreatedAsync();
|
||||
await scope.ServiceProvider.GetRequiredService<DatabaseSchemaMigrator>().ApplyAsync();
|
||||
await DataSeeder.SeedAsync(db);
|
||||
await DevDataSeeder.SeedIfEnabled(db, app.Configuration);
|
||||
}
|
||||
@@ -134,6 +224,7 @@ app.MapUserEndpoints();
|
||||
app.MapAiChatEndpoints();
|
||||
app.MapFileEndpoints();
|
||||
app.MapCalendarEndpoints();
|
||||
app.MapNotificationEndpoints();
|
||||
app.MapDoctorEndpoints();
|
||||
app.MapAdminEndpoints();
|
||||
app.MapFollowUpEndpoints();
|
||||
|
||||
Reference in New Issue
Block a user