From 4edcf063bcd50fbfec99338ad6393bf36fc45734 Mon Sep 17 00:00:00 2001 From: biss Date: Tue, 11 Aug 2026 12:07:15 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AD=A6=E4=B8=9A=E9=A2=84=E8=AD=A6=E6=A3=80?= =?UTF-8?q?=E6=B5=8B=E7=9A=84=E5=85=B6=E4=BD=99=E4=B8=89=E7=B1=BB=E8=A7=84?= =?UTF-8?q?=E5=88=99=E4=B9=9F=E5=B7=B2=E6=89=B9=E9=87=8F=E5=8C=96=EF=BC=9A?= =?UTF-8?q?=20=E4=BD=8E=E7=BB=A9=E7=82=B9=EF=BC=9A=E6=8C=89=E5=AD=A6?= =?UTF-8?q?=E7=94=9F=E5=88=86=E7=BB=84=E8=AE=A1=E7=AE=97=20GPA=E3=80=82=20?= =?UTF-8?q?=E7=BC=BA=E5=8B=A4/=E8=BF=9F=E5=88=B0=EF=BC=9A=E6=8C=89?= =?UTF-8?q?=E5=AD=A6=E7=94=9F=E5=88=86=E7=BB=84=E7=BB=9F=E8=AE=A1=E3=80=82?= =?UTF-8?q?=20=E5=BB=B6=E6=AF=95=E9=A3=8E=E9=99=A9=EF=BC=9A=E6=8C=89?= =?UTF-8?q?=E5=AD=A6=E7=94=9F=E5=88=86=E7=BB=84=E7=BB=9F=E8=AE=A1=E7=B4=AF?= =?UTF-8?q?=E8=AE=A1=E4=B8=8D=E5=8F=8A=E6=A0=BC=E3=80=82=20=E4=B8=89?= =?UTF-8?q?=E7=B1=BB=E8=A7=84=E5=88=99=E9=83=BD=E4=B8=80=E6=AC=A1=E6=80=A7?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E5=B7=B2=E6=9C=89=E9=A2=84=E8=AD=A6=E9=9B=86?= =?UTF-8?q?=E5=90=88=E5=90=8E=E5=8E=BB=E9=87=8D=EF=BC=8C=E4=B8=8D=E5=86=8D?= =?UTF-8?q?=E9=80=90=E5=AD=A6=E7=94=9F=E6=9F=A5=E5=BA=93=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/WarningsController.cs | 62 ++++++++----------- 1 file changed, 27 insertions(+), 35 deletions(-) diff --git a/src/Jiaowu.Api/Controllers/WarningsController.cs b/src/Jiaowu.Api/Controllers/WarningsController.cs index 2257c55..4d64e21 100644 --- a/src/Jiaowu.Api/Controllers/WarningsController.cs +++ b/src/Jiaowu.Api/Controllers/WarningsController.cs @@ -232,51 +232,43 @@ public sealed class WarningsController(AppDbContext db, ICurrentUserDataScope sc private async Task> DetectLowGPA(WarningRule rule, Guid termId, List students, CancellationToken ct) { - var records = new List(); - foreach (var s in students) - { - var grades = await db.GradeRecords.Where(x => x.StudentId == s.Id && x.GradeSheet!.TeachingTask!.AcademicTermId == termId && x.GradeSheet.Status == GradeSheetStatus.Published && x.GradePoint != null).ToListAsync(ct); - if (grades.Count == 0) continue; - var gpa = grades.Average(x => x.GradePoint!.Value); - if (gpa < rule.Threshold) - { - if (await db.WarningRecords.AnyAsync(x => x.StudentId == s.Id && x.AcademicTermId == termId && x.Type == WarningType.LowGPA, ct)) continue; - records.Add(new WarningRecord { StudentId = s.Id, Type = rule.Type, TriggerValue = Math.Round(gpa, 2), Detail = $"平均绩点 {gpa:F2},低于预警阈值 {rule.Threshold}。", AcademicTermId = termId }); - } - } - return records; + var studentIds = students.Select(student => student.Id).ToArray(); + var existingStudentIds = await ExistingWarningStudentIdsAsync(rule.Type, termId, ct); + var gpaByStudent = await db.GradeRecords.AsNoTracking() + .Where(record => studentIds.Contains(record.StudentId) && + record.GradeSheet!.TeachingTask!.AcademicTermId == termId && + record.GradeSheet.Status == GradeSheetStatus.Published && + record.GradePoint != null) + .GroupBy(record => record.StudentId) + .Select(group => new { StudentId = group.Key, Gpa = group.Average(record => record.GradePoint!.Value) }) + .ToListAsync(ct); + return gpaByStudent.Where(item => item.Gpa < rule.Threshold && !existingStudentIds.Contains(item.StudentId)) + .Select(item => new WarningRecord { StudentId = item.StudentId, Type = rule.Type, TriggerValue = Math.Round(item.Gpa, 2), Detail = $"平均绩点 {item.Gpa:F2},低于预警阈值 {rule.Threshold}。", AcademicTermId = termId }).ToList(); } private async Task> DetectAbsenteeism(WarningRule rule, Guid termId, List students, CancellationToken ct) { - var records = new List(); - foreach (var s in students) - { - var absent = await db.AttendanceRecords.CountAsync(x => x.StudentId == s.Id && x.AttendanceSheet!.Status == AttendanceSheetStatus.Submitted && x.AttendanceSheet.TeachingTask!.AcademicTermId == termId && (x.Status == AttendanceStatus.Absent || x.Status == AttendanceStatus.Late), ct); - if (absent >= rule.Threshold) - { - if (await db.WarningRecords.AnyAsync(x => x.StudentId == s.Id && x.AcademicTermId == termId && x.Type == WarningType.Absenteeism, ct)) continue; - records.Add(new WarningRecord { StudentId = s.Id, Type = rule.Type, TriggerValue = absent, Detail = $"缺勤/迟到 {absent} 次,达到预警阈值 {rule.Threshold} 次。", AcademicTermId = termId }); - } - } - return records; + var studentIds = students.Select(student => student.Id).ToArray(); + var existingStudentIds = await ExistingWarningStudentIdsAsync(rule.Type, termId, ct); + var absencesByStudent = await db.AttendanceRecords.AsNoTracking() + .Where(record => studentIds.Contains(record.StudentId) && record.AttendanceSheet!.Status == AttendanceSheetStatus.Submitted && record.AttendanceSheet.TeachingTask!.AcademicTermId == termId && (record.Status == AttendanceStatus.Absent || record.Status == AttendanceStatus.Late)) + .GroupBy(record => record.StudentId).Select(group => new { StudentId = group.Key, Absent = group.Count() }).ToListAsync(ct); + return absencesByStudent.Where(item => item.Absent >= rule.Threshold && !existingStudentIds.Contains(item.StudentId)) + .Select(item => new WarningRecord { StudentId = item.StudentId, Type = rule.Type, TriggerValue = item.Absent, Detail = $"缺勤/迟到 {item.Absent} 次,达到预警阈值 {rule.Threshold} 次。", AcademicTermId = termId }).ToList(); } private async Task> DetectGraduationDelay(WarningRule rule, Guid termId, List students, CancellationToken ct) { - var records = new List(); - foreach (var s in students) - { - var total = await db.GradeRecords.CountAsync(x => x.StudentId == s.Id && x.GradeSheet!.Status == GradeSheetStatus.Published && x.TotalScore < 60, ct); - if (total >= rule.Threshold) - { - if (await db.WarningRecords.AnyAsync(x => x.StudentId == s.Id && x.AcademicTermId == termId && x.Type == WarningType.GraduationDelay, ct)) continue; - records.Add(new WarningRecord { StudentId = s.Id, Type = rule.Type, TriggerValue = total, Detail = $"累计不及格 {total} 门,达到延毕预警阈值 {rule.Threshold} 门。", AcademicTermId = termId }); - } - } - return records; + var studentIds = students.Select(student => student.Id).ToArray(); + var existingStudentIds = await ExistingWarningStudentIdsAsync(rule.Type, termId, ct); + var failedByStudent = await db.GradeRecords.AsNoTracking().Where(record => studentIds.Contains(record.StudentId) && 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 { StudentId = item.StudentId, Type = rule.Type, TriggerValue = item.Failed, Detail = $"累计不及格 {item.Failed} 门,达到延毕预警阈值 {rule.Threshold} 门。", AcademicTermId = termId }).ToList(); } + private Task> ExistingWarningStudentIdsAsync(WarningType type, Guid termId, CancellationToken ct) => + db.WarningRecords.AsNoTracking().Where(record => record.AcademicTermId == termId && record.Type == type).Select(record => record.StudentId).ToHashSetAsync(ct); + private async Task GetStudentIdAsync(CancellationToken ct) => await db.Students.Where(s => s.UserId == scope.Current.UserId).Select(s => (Guid?)s.Id).FirstOrDefaultAsync(ct); private ActionResult StudentNotFound() => Conflict(new ProblemDetails { Title = "未关联学生档案", Status = 409 }); private ActionResult ConflictProblem(string d) => Conflict(new ProblemDetails { Title = "操作失败", Detail = d, Status = 409 });