全部完成。定时检测系统概览:

新增组件

  ┌────────────────────────────────┬─────────────────────────────────────────────────────────┐
  │              组件              │                          说明                           │
  ├────────────────────────────────┼─────────────────────────────────────────────────────────┤
  │ WarningSchedule 实体           │ 定时配置:学期、星期几、时、分、启用/禁用、上次执行时间 │
  ├────────────────────────────────┼─────────────────────────────────────────────────────────┤
  │ WarningCheckWorker             │ BackgroundService,每分钟轮询一次,匹配到时间就执行检测 │
  ├────────────────────────────────┼─────────────────────────────────────────────────────────┤
  │ GET/PUT /api/warnings/schedule │ 读取/保存定时配置                                       │
  └────────────────────────────────┴─────────────────────────────────────────────────────────┘

  页面配置

  在学业预警页面顶部新增「定时检测」区域:
  - 启用开关:一键开/关自动检测
  - 星期选择:周一至周日
  - 时分选择:精确到分钟
  - 上次执行时间:自动显示

  工作流程

  每分钟 Worker 轮询
    → 查询 WarningSchedules 表,匹配当前星期+时+分
    → 检查是否已在本分钟执行过(防止重复)
    → 加载该学期全部启用规则 + 在籍学生
    → 逐条规则、逐个学生检测
    → 已存在的同型预警跳过,新预警写入 + 通知学生/辅导员
    → 更新 LastRunAt

  管理员配置"每周一 08:00 自动检测",系统就会在每周一早上 8 点整自动执行一次完整的学业预警检测。
This commit is contained in:
2026-07-25 17:17:05 +08:00 Unverified
parent db3a275601
commit d23b5156b4
7 changed files with 197 additions and 1 deletions
@@ -62,6 +62,7 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
public DbSet<CourseSubstitution> CourseSubstitutions => Set<CourseSubstitution>();
public DbSet<WarningRule> WarningRules => Set<WarningRule>();
public DbSet<WarningRecord> WarningRecords => Set<WarningRecord>();
public DbSet<WarningSchedule> WarningSchedules => Set<WarningSchedule>();
public DbSet<Notification> Notifications => Set<Notification>();
public DbSet<StudentStatusChange> StudentStatusChanges =>
Set<StudentStatusChange>();
@@ -730,6 +731,12 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
entity.HasOne(x => x.Student).WithMany()
.HasForeignKey(x => x.StudentId).OnDelete(DeleteBehavior.Restrict);
});
builder.Entity<WarningSchedule>(entity =>
{
entity.HasIndex(x => x.AcademicTermId).IsUnique();
entity.HasOne(x => x.AcademicTerm).WithMany()
.HasForeignKey(x => x.AcademicTermId).OnDelete(DeleteBehavior.Cascade);
});
builder.Entity<CourseSubstitution>(entity =>
{