优化学业预警检测:
通知阶段移除逐条查询学生、规则、辅导员的 N+1 数据库访问。 不及格预警从“每个学生一次统计 + 一次查重”改为批量分组统计与一次性已有记录集合查询。 保持原有阈值、去重、通知内容与权限行为。
This commit is contained in:
@@ -101,17 +101,23 @@ public sealed class WarningsController(AppDbContext db, ICurrentUserDataScope sc
|
|||||||
db.WarningRecords.AddRange(generated);
|
db.WarningRecords.AddRange(generated);
|
||||||
await db.SaveChangesAsync(ct);
|
await db.SaveChangesAsync(ct);
|
||||||
|
|
||||||
|
var studentsById = studentInfos.ToDictionary(student => student.Id);
|
||||||
|
var rulesByType = rules.ToDictionary(rule => rule.Type);
|
||||||
|
var classIds = studentInfos.Select(student => student.ClassId).Distinct().ToArray();
|
||||||
|
var counselorsByClass = await db.AdministrativeClasses.AsNoTracking()
|
||||||
|
.WhereIn(classIds, item => item.Id)
|
||||||
|
.Where(item => item.CounselorUserId != null)
|
||||||
|
.Select(item => new { item.Id, CounselorUserId = item.CounselorUserId!.Value })
|
||||||
|
.ToDictionaryAsync(item => item.Id, item => item.CounselorUserId, ct);
|
||||||
foreach (var w in generated)
|
foreach (var w in generated)
|
||||||
{
|
{
|
||||||
var si = studentInfos.First(s => s.Id == w.StudentId);
|
var si = studentsById[w.StudentId];
|
||||||
var r = rules.First(r => r.Type == w.Type);
|
var r = rulesByType[w.Type];
|
||||||
if (r.NotifyStudent && si.UserId.HasValue)
|
if (r.NotifyStudent && si.UserId.HasValue)
|
||||||
await NotificationService.SendAsync(db, si.UserId.Value, "学业预警", w.Detail, "/warnings", ct, NotificationCategory.Warning);
|
await NotificationService.SendAsync(db, si.UserId.Value, "学业预警", w.Detail, "/warnings", ct, NotificationCategory.Warning);
|
||||||
if (r.NotifyCounselor)
|
if (r.NotifyCounselor && counselorsByClass.TryGetValue(si.ClassId, out var counselorId))
|
||||||
{
|
{
|
||||||
var counselorId = await db.AdministrativeClasses.Where(c => c.Id == si.ClassId && c.CounselorUserId != null).Select(c => c.CounselorUserId!.Value).FirstOrDefaultAsync(ct);
|
await NotificationService.SendAsync(db, counselorId, "学生学业预警", $"{si.Name}:{w.Detail}", "/warnings", ct, NotificationCategory.Warning);
|
||||||
if (counselorId != default)
|
|
||||||
await NotificationService.SendAsync(db, counselorId, "学生学业预警", $"{si.Name}:{w.Detail}", "/warnings", ct, NotificationCategory.Warning);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -197,17 +203,31 @@ public sealed class WarningsController(AppDbContext db, ICurrentUserDataScope sc
|
|||||||
// ═══════════ Detection logic ═══════════
|
// ═══════════ Detection logic ═══════════
|
||||||
private async Task<List<WarningRecord>> DetectFailedCredits(WarningRule rule, Guid termId, List<StudentInfo> students, CancellationToken ct)
|
private async Task<List<WarningRecord>> DetectFailedCredits(WarningRule rule, Guid termId, List<StudentInfo> students, CancellationToken ct)
|
||||||
{
|
{
|
||||||
var records = new List<WarningRecord>();
|
var studentIds = students.Select(student => student.Id).ToArray();
|
||||||
foreach (var s in students)
|
var existingStudentIds = await db.WarningRecords.AsNoTracking()
|
||||||
{
|
.Where(record => record.AcademicTermId == termId && record.Type == rule.Type)
|
||||||
var failed = await db.GradeRecords.CountAsync(x => x.StudentId == s.Id && x.GradeSheet!.TeachingTask!.AcademicTermId == termId && x.GradeSheet.Status == GradeSheetStatus.Published && x.TotalScore < 60, ct);
|
.Select(record => record.StudentId)
|
||||||
if (failed >= rule.Threshold)
|
.ToHashSetAsync(ct);
|
||||||
|
var failedByStudent = await db.GradeRecords.AsNoTracking()
|
||||||
|
.Where(record => studentIds.Contains(record.StudentId) &&
|
||||||
|
record.GradeSheet!.TeachingTask!.AcademicTermId == termId &&
|
||||||
|
record.GradeSheet.Status == GradeSheetStatus.Published &&
|
||||||
|
record.TotalScore < 60)
|
||||||
|
.GroupBy(record => record.StudentId)
|
||||||
|
.Select(group => new { StudentId = group.Key, Failed = group.Count() })
|
||||||
|
.ToListAsync(ct);
|
||||||
|
return failedByStudent
|
||||||
|
.Where(item => item.Failed >= rule.Threshold &&
|
||||||
|
!existingStudentIds.Contains(item.StudentId))
|
||||||
|
.Select(item => new WarningRecord
|
||||||
{
|
{
|
||||||
if (await db.WarningRecords.AnyAsync(x => x.StudentId == s.Id && x.AcademicTermId == termId && x.Type == WarningType.FailedCredits, ct)) continue;
|
StudentId = item.StudentId,
|
||||||
records.Add(new WarningRecord { StudentId = s.Id, Type = rule.Type, TriggerValue = failed, Detail = $"不及格课程 {failed} 门,达到预警阈值 {rule.Threshold} 门。", AcademicTermId = termId });
|
Type = rule.Type,
|
||||||
}
|
TriggerValue = item.Failed,
|
||||||
}
|
Detail = $"不及格课程 {item.Failed} 门,达到预警阈值 {rule.Threshold} 门。",
|
||||||
return records;
|
AcademicTermId = termId
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<List<WarningRecord>> DetectLowGPA(WarningRule rule, Guid termId, List<StudentInfo> students, CancellationToken ct)
|
private async Task<List<WarningRecord>> DetectLowGPA(WarningRule rule, Guid termId, List<StudentInfo> students, CancellationToken ct)
|
||||||
|
|||||||
Reference in New Issue
Block a user