全部完成。定时检测系统概览:
新增组件
┌────────────────────────────────┬─────────────────────────────────────────────────────────┐
│ 组件 │ 说明 │
├────────────────────────────────┼─────────────────────────────────────────────────────────┤
│ WarningSchedule 实体 │ 定时配置:学期、星期几、时、分、启用/禁用、上次执行时间 │
├────────────────────────────────┼─────────────────────────────────────────────────────────┤
│ WarningCheckWorker │ BackgroundService,每分钟轮询一次,匹配到时间就执行检测 │
├────────────────────────────────┼─────────────────────────────────────────────────────────┤
│ GET/PUT /api/warnings/schedule │ 读取/保存定时配置 │
└────────────────────────────────┴─────────────────────────────────────────────────────────┘
页面配置
在学业预警页面顶部新增「定时检测」区域:
- 启用开关:一键开/关自动检测
- 星期选择:周一至周日
- 时分选择:精确到分钟
- 上次执行时间:自动显示
工作流程
每分钟 Worker 轮询
→ 查询 WarningSchedules 表,匹配当前星期+时+分
→ 检查是否已在本分钟执行过(防止重复)
→ 加载该学期全部启用规则 + 在籍学生
→ 逐条规则、逐个学生检测
→ 已存在的同型预警跳过,新预警写入 + 通知学生/辅导员
→ 更新 LastRunAt
管理员配置"每周一 08:00 自动检测",系统就会在每周一早上 8 点整自动执行一次完整的学业预警检测。
This commit is contained in:
1 parent
db3a275601
commit
d23b5156b4
7 files changed
+197
-1
No files matched your search
@@ -115,6 +115,26 @@ public sealed class WarningsController(AppDbContext db, ICurrentUserDataScope sc
|
||||
return Ok(await q.OrderByDescending(x => x.CreatedAt).Select(x => new { x.Id, x.StudentId, StudentName = x.Student!.Name, StudentNumber = x.Student.StudentNumber, ClassName = x.Student.AdministrativeClass!.Name, x.Type, x.Status, x.TriggerValue, x.Detail, x.AcknowledgedAt, x.AcknowledgeComment, x.CreatedAt }).ToListAsync(ct));
|
||||
}
|
||||
|
||||
// ═══════════ Schedule ═══════════
|
||||
[HttpGet("schedule")]
|
||||
[Authorize(Roles = Managers)]
|
||||
public async Task<ActionResult> GetSchedule(Guid academicTermId, CancellationToken ct)
|
||||
{
|
||||
var s = await db.WarningSchedules.AsNoTracking().FirstOrDefaultAsync(x => x.AcademicTermId == academicTermId, ct);
|
||||
return Ok(s is null ? new { isEnabled = false, dayOfWeek = 1, hour = 8, minute = 0, lastRunAt = (DateTime?)null } : new { s.IsEnabled, s.DayOfWeek, s.Hour, s.Minute, s.LastRunAt });
|
||||
}
|
||||
|
||||
[HttpPut("schedule")]
|
||||
[Authorize(Roles = Managers)]
|
||||
public async Task<ActionResult> SaveSchedule(Guid academicTermId, [FromBody] ScheduleDto dto, CancellationToken ct)
|
||||
{
|
||||
var s = await db.WarningSchedules.FirstOrDefaultAsync(x => x.AcademicTermId == academicTermId, ct);
|
||||
if (s is null) { s = new WarningSchedule { AcademicTermId = academicTermId }; db.WarningSchedules.Add(s); }
|
||||
s.IsEnabled = dto.IsEnabled; s.DayOfWeek = dto.DayOfWeek; s.Hour = dto.Hour; s.Minute = dto.Minute;
|
||||
await db.SaveChangesAsync(ct);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
// ═══════════ Student ═══════════
|
||||
[HttpGet("my-warnings")]
|
||||
[Authorize(Roles = SystemRoles.Student)]
|
||||
@@ -213,4 +233,5 @@ public sealed class WarningsController(AppDbContext db, ICurrentUserDataScope sc
|
||||
|
||||
public sealed record StudentInfo(Guid Id, Guid? UserId, string Name, Guid ClassId);
|
||||
public sealed record WarningRuleDto(WarningType Type, [MaxLength(100)] string Name, decimal Threshold, bool IsEnabled, bool NotifyStudent, bool NotifyCounselor, [MaxLength(300)] string? Description);
|
||||
public sealed record ScheduleDto(bool IsEnabled, int DayOfWeek, int Hour, int Minute);
|
||||
public sealed record AckBody([MaxLength(300)] string? Comment);
|
||||
@@ -36,6 +36,17 @@ public enum WarningType
|
||||
GraduationDelay = 4
|
||||
}
|
||||
|
||||
public sealed class WarningSchedule : EntityBase
|
||||
{
|
||||
public Guid AcademicTermId { get; set; }
|
||||
public AcademicTerm? AcademicTerm { get; set; }
|
||||
public bool IsEnabled { get; set; }
|
||||
public int DayOfWeek { get; set; } = 1;
|
||||
public int Hour { get; set; } = 8;
|
||||
public int Minute { get; set; }
|
||||
public DateTime? LastRunAt { get; set; }
|
||||
}
|
||||
|
||||
public enum WarningStatus
|
||||
{
|
||||
Active = 1,
|
||||
|
||||
@@ -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 =>
|
||||
{
|
||||
|
||||
Loaded 3 of 7 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user