feat: 后端架构重构 — Endpoint→Service→Repository分层 + AI确认机制 + 异步任务持久化

- 核心业务拆分为 Endpoint → Application Service → Repository 三层
- AI写入操作必须用户确认后才写库(确认卡片机制)
- 报告/饮食/用药分析改为持久化任务队列(原子领取/重试/重启恢复)
- 运动计划修复: 连续真实日期替代周模板
- 用药提醒去重 + 通知Outbox预留
- 认证收拢到AuthService, 管理员收拢到AdminService
- AI会话加用户归属校验防串号
- 提示词调整为患者视角
- 开发假数据已关闭
- 21/21测试通过, 0警告0错误
This commit is contained in:
MingNian
2026-06-20 20:41:42 +08:00
parent c610417e29
commit 4d213b5a44
132 changed files with 6733 additions and 2856 deletions

View File

@@ -1,5 +1,6 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Health.Infrastructure.Data.Records;
namespace Health.Infrastructure.Data;
@@ -33,6 +34,11 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options) : DbCon
public DbSet<VerificationCode> VerificationCodes => Set<VerificationCode>();
public DbSet<NotificationPreference> NotificationPreferences => Set<NotificationPreference>();
public DbSet<DeviceToken> DeviceTokens => Set<DeviceToken>();
public DbSet<AiWriteCommandRecord> AiWriteCommands => Set<AiWriteCommandRecord>();
public DbSet<ReportAnalysisTaskRecord> ReportAnalysisTasks => Set<ReportAnalysisTaskRecord>();
public DbSet<DietImageAnalysisTaskRecord> DietImageAnalysisTasks => Set<DietImageAnalysisTaskRecord>();
public DbSet<MedicationReminderTaskRecord> MedicationReminderTasks => Set<MedicationReminderTaskRecord>();
public DbSet<NotificationOutboxRecord> NotificationOutbox => Set<NotificationOutboxRecord>();
protected override void OnModelCreating(ModelBuilder builder)
{
@@ -95,7 +101,12 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options) : DbCon
// ---- ExercisePlan ----
builder.Entity<ExercisePlan>(e =>
{
e.HasIndex(p => new { p.UserId, p.WeekStartDate }).IsDescending(false, true);
e.HasIndex(p => new { p.UserId, p.StartDate, p.EndDate });
});
builder.Entity<ExercisePlanItem>(e =>
{
e.HasIndex(i => new { i.PlanId, i.ScheduledDate }).IsUnique();
});
// ---- Report ----
@@ -148,5 +159,48 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options) : DbCon
{
e.HasIndex(a => a.UserId).IsUnique();
});
builder.Entity<AiWriteCommandRecord>(e =>
{
e.ToTable("AiWriteCommands");
e.HasIndex(x => new { x.UserId, x.Status, x.ExpiresAt });
e.Property(x => x.ToolName).HasMaxLength(64);
e.Property(x => x.Status).HasMaxLength(32);
e.Property(x => x.Arguments).HasColumnType("jsonb");
});
builder.Entity<ReportAnalysisTaskRecord>(e =>
{
e.ToTable("ReportAnalysisTasks");
e.HasIndex(x => new { x.Status, x.AvailableAt });
e.HasIndex(x => x.ReportId);
e.Property(x => x.Status).HasMaxLength(32);
});
builder.Entity<DietImageAnalysisTaskRecord>(e =>
{
e.ToTable("DietImageAnalysisTasks");
e.HasIndex(x => new { x.Status, x.AvailableAt });
e.Property(x => x.FilePaths).HasColumnType("jsonb");
e.Property(x => x.Status).HasMaxLength(32);
});
builder.Entity<MedicationReminderTaskRecord>(e =>
{
e.ToTable("MedicationReminderTasks");
e.HasIndex(x => new { x.Status, x.AvailableAt });
e.HasIndex(x => new { x.MedicationId, x.ScheduledDate, x.ScheduledTime }).IsUnique();
e.Property(x => x.Status).HasMaxLength(32);
});
builder.Entity<NotificationOutboxRecord>(e =>
{
e.ToTable("NotificationOutbox");
e.HasIndex(x => x.SourceTaskId).IsUnique();
e.HasIndex(x => new { x.Status, x.AvailableAt });
e.Property(x => x.Type).HasMaxLength(64);
e.Property(x => x.Status).HasMaxLength(32);
e.Property(x => x.Payload).HasColumnType("jsonb");
});
}
}