排课计算、课表发布、补考自动编排三类任务统一接入消息系统。

业务任务与 Outbox 消息同一事务落库,避免“数据库成功但消息没发出去”。
RabbitMQ 使用持久化消息、发布确认、手动 ACK、Quorum Queue、死信队列。
增加处理租约、心跳、异常重试、最大重试次数和幂等状态控制。
保留 InMemory 模式,开发环境无需安装 RabbitMQ。
支持多实例竞争消费,后续可以横向扩容。
新增 /health/messaging 消息系统健康检查。
This commit is contained in:
2026-07-26 20:51:42 +08:00 Unverified
parent 9f534c22f4
commit 6300645120
28 changed files with 6513 additions and 313 deletions
@@ -58,6 +58,8 @@ public sealed class DevelopmentSqliteMigrator(
"20260726_31_unified_message_center";
private const string ClassroomReservationsMigration =
"20260726_32_classroom_reservations";
private const string BackgroundJobOutboxMigration =
"20260726_33_background_job_outbox";
public async Task MigrateAsync(CancellationToken cancellationToken = default)
{
@@ -421,6 +423,19 @@ public sealed class DevelopmentSqliteMigrator(
ClassroomReservationsMigration,
classroomReservationsExist ? [] : ClassroomReservationStatements,
cancellationToken);
var backgroundJobOutboxExists = await db.Database
.SqlQueryRaw<int>(
"""
SELECT COUNT(*) AS "Value"
FROM sqlite_master
WHERE type = 'table' AND name = 'BackgroundJobOutboxMessages'
""")
.AnyAsync(value => value > 0, cancellationToken);
await ApplyMigrationAsync(
BackgroundJobOutboxMigration,
backgroundJobOutboxExists ? [] : BackgroundJobOutboxStatements,
cancellationToken);
}
private async Task ApplyMigrationAsync(
@@ -2004,4 +2019,37 @@ public sealed class DevelopmentSqliteMigrator(
ON "ClassroomReservations" ("ReviewedByUserId");
"""
];
private static readonly string[] BackgroundJobOutboxStatements =
[
"""
CREATE TABLE "BackgroundJobOutboxMessages" (
"Id" TEXT NOT NULL CONSTRAINT "PK_BackgroundJobOutboxMessages" PRIMARY KEY,
"JobKind" INTEGER NOT NULL,
"JobId" TEXT NOT NULL,
"State" INTEGER NOT NULL,
"PublishAttempts" INTEGER NOT NULL,
"ProcessingAttempts" INTEGER NOT NULL,
"PublishedAt" TEXT NULL,
"ProcessingToken" TEXT NULL,
"LeaseExpiresAt" TEXT NULL,
"CompletedAt" TEXT NULL,
"LastError" TEXT NULL,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL
);
""",
"""
CREATE UNIQUE INDEX "IX_BackgroundJobOutboxMessages_JobKind_JobId"
ON "BackgroundJobOutboxMessages" ("JobKind", "JobId");
""",
"""
CREATE INDEX "IX_BackgroundJobOutboxMessages_LeaseExpiresAt"
ON "BackgroundJobOutboxMessages" ("LeaseExpiresAt");
""",
"""
CREATE INDEX "IX_BackgroundJobOutboxMessages_State_CreatedAt"
ON "BackgroundJobOutboxMessages" ("State", "CreatedAt");
"""
];
}