“教学班成绩分析”页面新增“定时刷新设置”,支持启停、刷新间隔、单次处理上限、查看上次/下次扫描时间。

配置保存在独立数据库表 CourseGradeStatisticsRefreshSettings,不是修改 appsettings.json。
后台每 10 秒读取配置,仅到期扫描;成绩录入、导入、审批时不再立即创建统计任务。
扫描发现过期数据后,仍通过持久任务、Outbox 和 RabbitMQ 执行;Redis统计缓存由处理任务统一刷新。
This commit is contained in:
biss committed 2026-08-09 20:51:57 +08:00
1 parent e8261714da
commit cd073885b5
17 files changed
+7659 -44

No files matched your search

+1
View File
@@ -18,6 +18,7 @@ BackgroundJobs__AutomaticScheduleConcurrency=1
BackgroundJobs__SchedulePublishConcurrency=1
BackgroundJobs__MakeupExamAutoConcurrency=1
BackgroundJobs__ExamArrangementConcurrency=1
BackgroundJobs__CourseGradeStatisticsRefreshConcurrency=1
# RabbitMq__HostName=rabbitmq.example.edu.cn
# RabbitMq__Port=5671
# RabbitMq__UserName=jiaowu
@@ -251,13 +251,6 @@ public sealed class ApprovalsController(AppDbContext db, ICurrentUserDataScope s
// Auto-apply: update grade record
gm.GradeRecord!.TotalScore = gm.RequestedScore;
gm.GradeRecord.GradePoint = GradeCalculator.CalculateGradePoint(gm.RequestedScore);
var statisticsJob = new CourseGradeStatisticsRefreshJob
{
GradeSheetId = gm.GradeRecord.GradeSheetId
};
db.CourseGradeStatisticsRefreshJobs.Add(statisticsJob);
db.BackgroundJobOutboxMessages.Add(BackgroundJobOutboxMessage.Create(
BackgroundJobKind.CourseGradeStatisticsRefresh, statisticsJob.Id));
await db.SaveChangesAsync(ct);
await NotificationService.SendAsync(
db,
@@ -25,6 +25,69 @@ public sealed class GradeAnalyticsController(
SystemRoles.CollegeAdmin + "," +
SystemRoles.Leader + "," +
SystemRoles.Teacher;
private const string ScheduleManagers =
SystemRoles.SuperAdmin + "," + SystemRoles.AcademicAdmin;
[HttpGet("refresh-schedule")]
[Authorize(Roles = ScheduleManagers)]
public async Task<ActionResult> GetRefreshSchedule(CancellationToken cancellationToken)
{
var setting = await db.CourseGradeStatisticsRefreshSettings.AsNoTracking()
.SingleOrDefaultAsync(
x => x.Key == CourseGradeStatisticsRefreshSetting.DefaultKey,
cancellationToken);
var defaults = new CourseGradeStatisticsRefreshSetting();
var enabled = setting?.IsEnabled ?? defaults.IsEnabled;
var intervalSeconds = setting?.IntervalSeconds ?? defaults.IntervalSeconds;
var batchSize = setting?.BatchSize ?? defaults.BatchSize;
var lastRunAt = setting?.LastRunAt;
return Ok(new
{
IsEnabled = enabled,
IntervalSeconds = intervalSeconds,
BatchSize = batchSize,
LastRunAt = lastRunAt,
NextRunAt = enabled && lastRunAt.HasValue
? lastRunAt.Value.AddSeconds(intervalSeconds)
: null as DateTime?
});
}
[HttpPut("refresh-schedule")]
[Authorize(Roles = ScheduleManagers)]
public async Task<ActionResult> SaveRefreshSchedule(
SaveGradeStatisticsRefreshScheduleRequest request,
CancellationToken cancellationToken)
{
if (request.IntervalSeconds is < 10 or > 86400)
return BadRequest(new ProblemDetails
{
Title = "刷新间隔应在 10 秒到 24 小时之间。",
Status = StatusCodes.Status400BadRequest
});
if (request.BatchSize is < 1 or > 5000)
return BadRequest(new ProblemDetails
{
Title = "单次刷新批量应在 1 到 5000 之间。",
Status = StatusCodes.Status400BadRequest
});
var setting = await db.CourseGradeStatisticsRefreshSettings
.SingleOrDefaultAsync(
x => x.Key == CourseGradeStatisticsRefreshSetting.DefaultKey,
cancellationToken);
if (setting is null)
{
setting = new CourseGradeStatisticsRefreshSetting();
db.CourseGradeStatisticsRefreshSettings.Add(setting);
}
setting.IsEnabled = request.IsEnabled;
setting.IntervalSeconds = request.IntervalSeconds;
setting.BatchSize = request.BatchSize;
await db.SaveChangesAsync(cancellationToken);
return NoContent();
}
[HttpGet("teaching-classes")]
public async Task<ActionResult> GetTeachingClasses(
@@ -526,3 +589,8 @@ public sealed class GradeAnalyticsController(
IReadOnlyList<HistoricalComparison> History,
ComparisonDelta? UniversityDelta);
}
public sealed record SaveGradeStatisticsRefreshScheduleRequest(
bool IsEnabled,
int IntervalSeconds,
int BatchSize);
Loaded 3 of 17 files, more files were not shown because too many files have changed in this diff. Show more