主要改动:
自动排课接口立即返回 202 + jobId 后台服务独立执行,不受浏览器关闭或前端超时影响 进度、成功、失败状态持久化到数据库 服务重启后自动恢复排队中或中断的任务 同一草稿禁止重复提交后台任务 运行期间禁止编辑、发布或删除该课表 排课结果和任务成功状态在同一事务中提交 前端每秒轮询进度,显示已处理教学班和已规划安排数
This commit is contained in:
@@ -6,8 +6,15 @@ namespace Jiaowu.Api.Infrastructure.Scheduling;
|
||||
|
||||
public sealed class AutomaticScheduleGenerator(AppDbContext db)
|
||||
{
|
||||
public Task<AutomaticScheduleResult> GenerateAsync(
|
||||
SchedulePlan plan,
|
||||
CancellationToken cancellationToken) =>
|
||||
GenerateAsync(plan, null, true, cancellationToken);
|
||||
|
||||
public async Task<AutomaticScheduleResult> GenerateAsync(
|
||||
SchedulePlan plan,
|
||||
Func<AutomaticScheduleProgress, CancellationToken, Task>? reportProgress,
|
||||
bool saveChanges,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var timeSlots = await db.ScheduleTimeSlots.AsNoTracking()
|
||||
@@ -50,9 +57,18 @@ public sealed class AutomaticScheduleGenerator(AppDbContext db)
|
||||
|
||||
var created = 0;
|
||||
var completedTasks = 0;
|
||||
var processedTasks = 0;
|
||||
var messages = new List<string>();
|
||||
if (reportProgress is not null)
|
||||
{
|
||||
await reportProgress(
|
||||
new(tasks.Count, 0, 0, 0),
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
foreach (var task in tasks)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
constraints.TryGetValue(task.Id, out var constraint);
|
||||
var scheduledHours = entries
|
||||
.Where(x => x.TeachingTaskId == task.Id)
|
||||
@@ -61,6 +77,13 @@ public sealed class AutomaticScheduleGenerator(AppDbContext db)
|
||||
if (remainingHours == 0)
|
||||
{
|
||||
completedTasks++;
|
||||
processedTasks++;
|
||||
if (reportProgress is not null)
|
||||
{
|
||||
await reportProgress(
|
||||
new(tasks.Count, processedTasks, created, completedTasks),
|
||||
cancellationToken);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -74,7 +97,8 @@ public sealed class AutomaticScheduleGenerator(AppDbContext db)
|
||||
desiredBlock,
|
||||
activePeriods,
|
||||
classrooms,
|
||||
entries);
|
||||
entries,
|
||||
cancellationToken);
|
||||
if (candidate is null && desiredBlock > 1)
|
||||
{
|
||||
candidate = FindBestCandidate(
|
||||
@@ -84,7 +108,8 @@ public sealed class AutomaticScheduleGenerator(AppDbContext db)
|
||||
1,
|
||||
activePeriods,
|
||||
classrooms,
|
||||
entries);
|
||||
entries,
|
||||
cancellationToken);
|
||||
}
|
||||
if (candidate is null) break;
|
||||
|
||||
@@ -103,11 +128,24 @@ public sealed class AutomaticScheduleGenerator(AppDbContext db)
|
||||
messages.Add(
|
||||
$"{task.TaskNumber} · {task.Name} 仍有 {remainingHours} 学时无法安排,请检查教师/班级冲突或场地与时间约束。");
|
||||
}
|
||||
|
||||
processedTasks++;
|
||||
if (reportProgress is not null)
|
||||
{
|
||||
await reportProgress(
|
||||
new(tasks.Count, processedTasks, created, completedTasks),
|
||||
cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
if (created > 0)
|
||||
if (created > 0 && saveChanges)
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
return new(created, completedTasks, messages);
|
||||
return new(
|
||||
created,
|
||||
completedTasks,
|
||||
messages,
|
||||
tasks.Count,
|
||||
processedTasks);
|
||||
}
|
||||
|
||||
private static ScheduleEntry? FindBestCandidate(
|
||||
@@ -117,7 +155,8 @@ public sealed class AutomaticScheduleGenerator(AppDbContext db)
|
||||
int periodCount,
|
||||
HashSet<int> activePeriods,
|
||||
IReadOnlyList<Classroom> classrooms,
|
||||
IReadOnlyList<ScheduleEntry> entries)
|
||||
IReadOnlyList<ScheduleEntry> entries,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var allowedDays = ParseAllowedDays(constraint?.AllowedDayOfWeeks);
|
||||
var firstPeriod = constraint?.EarliestPeriod ?? activePeriods.Min();
|
||||
@@ -129,8 +168,10 @@ public sealed class AutomaticScheduleGenerator(AppDbContext db)
|
||||
var candidates = new List<(ScheduleEntry Entry, int Score)>();
|
||||
foreach (var day in allowedDays)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
for (var start = firstPeriod; start + periodCount - 1 <= lastPeriod; start++)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (Enumerable.Range(start, periodCount).Any(period => !activePeriods.Contains(period)))
|
||||
continue;
|
||||
|
||||
@@ -145,7 +186,6 @@ public sealed class AutomaticScheduleGenerator(AppDbContext db)
|
||||
TeachingTaskId = task.Id,
|
||||
TeachingTask = task,
|
||||
ClassroomId = room?.Id,
|
||||
Classroom = room,
|
||||
DayOfWeek = day,
|
||||
StartPeriod = start,
|
||||
PeriodCount = periodCount,
|
||||
@@ -216,4 +256,12 @@ public sealed class AutomaticScheduleGenerator(AppDbContext db)
|
||||
public sealed record AutomaticScheduleResult(
|
||||
int CreatedEntries,
|
||||
int CompletedTasks,
|
||||
IReadOnlyList<string> Messages);
|
||||
IReadOnlyList<string> Messages,
|
||||
int TotalTasks = 0,
|
||||
int ProcessedTasks = 0);
|
||||
|
||||
public sealed record AutomaticScheduleProgress(
|
||||
int TotalTasks,
|
||||
int ProcessedTasks,
|
||||
int CreatedEntries,
|
||||
int CompletedTasks);
|
||||
|
||||
Reference in New Issue
Block a user