feat: SKU保存链路切换到RabbitMQ Outbox并新增独立Worker
All checks were successful
Build and Deploy TenantApi / build-and-deploy (push) Successful in 51s

This commit is contained in:
2026-02-25 11:20:38 +08:00
parent aeef4ca649
commit 77caac3af9
13 changed files with 9475 additions and 18 deletions

View File

@@ -1,5 +1,6 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using MassTransit;
using TakeoutSaaS.Domain.Analytics.Entities;
using TakeoutSaaS.Domain.Coupons.Entities;
using TakeoutSaaS.Domain.CustomerService.Entities;
@@ -539,6 +540,9 @@ public sealed class TakeoutAppDbContext(
ConfigureMetricDefinition(modelBuilder.Entity<MetricDefinition>());
ConfigureMetricSnapshot(modelBuilder.Entity<MetricSnapshot>());
ConfigureMetricAlertRule(modelBuilder.Entity<MetricAlertRule>());
modelBuilder.AddInboxStateEntity();
modelBuilder.AddOutboxMessageEntity();
modelBuilder.AddOutboxStateEntity();
// 3. 应用多租户全局查询过滤器
ApplyTenantQueryFilters(modelBuilder);

View File

@@ -0,0 +1,142 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace TakeoutSaaS.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class AddAppOutboxForSkuMessaging : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "InboxState",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
MessageId = table.Column<Guid>(type: "uuid", nullable: false),
ConsumerId = table.Column<Guid>(type: "uuid", nullable: false),
LockId = table.Column<Guid>(type: "uuid", nullable: false),
RowVersion = table.Column<byte[]>(type: "bytea", rowVersion: true, nullable: true),
Received = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
ReceiveCount = table.Column<int>(type: "integer", nullable: false),
ExpirationTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
Consumed = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
Delivered = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
LastSequenceNumber = table.Column<long>(type: "bigint", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_InboxState", x => x.Id);
table.UniqueConstraint("AK_InboxState_MessageId_ConsumerId", x => new { x.MessageId, x.ConsumerId });
});
migrationBuilder.CreateTable(
name: "OutboxState",
columns: table => new
{
OutboxId = table.Column<Guid>(type: "uuid", nullable: false),
LockId = table.Column<Guid>(type: "uuid", nullable: false),
RowVersion = table.Column<byte[]>(type: "bytea", rowVersion: true, nullable: true),
Created = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
Delivered = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
LastSequenceNumber = table.Column<long>(type: "bigint", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_OutboxState", x => x.OutboxId);
});
migrationBuilder.CreateTable(
name: "OutboxMessage",
columns: table => new
{
SequenceNumber = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
EnqueueTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
SentTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
Headers = table.Column<string>(type: "text", nullable: true),
Properties = table.Column<string>(type: "text", nullable: true),
InboxMessageId = table.Column<Guid>(type: "uuid", nullable: true),
InboxConsumerId = table.Column<Guid>(type: "uuid", nullable: true),
OutboxId = table.Column<Guid>(type: "uuid", nullable: true),
MessageId = table.Column<Guid>(type: "uuid", nullable: false),
ContentType = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
MessageType = table.Column<string>(type: "text", nullable: false),
Body = table.Column<string>(type: "text", nullable: false),
ConversationId = table.Column<Guid>(type: "uuid", nullable: true),
CorrelationId = table.Column<Guid>(type: "uuid", nullable: true),
InitiatorId = table.Column<Guid>(type: "uuid", nullable: true),
RequestId = table.Column<Guid>(type: "uuid", nullable: true),
SourceAddress = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
DestinationAddress = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
ResponseAddress = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
FaultAddress = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
ExpirationTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_OutboxMessage", x => x.SequenceNumber);
table.ForeignKey(
name: "FK_OutboxMessage_InboxState_InboxMessageId_InboxConsumerId",
columns: x => new { x.InboxMessageId, x.InboxConsumerId },
principalTable: "InboxState",
principalColumns: new[] { "MessageId", "ConsumerId" });
table.ForeignKey(
name: "FK_OutboxMessage_OutboxState_OutboxId",
column: x => x.OutboxId,
principalTable: "OutboxState",
principalColumn: "OutboxId");
});
migrationBuilder.CreateIndex(
name: "IX_InboxState_Delivered",
table: "InboxState",
column: "Delivered");
migrationBuilder.CreateIndex(
name: "IX_OutboxMessage_EnqueueTime",
table: "OutboxMessage",
column: "EnqueueTime");
migrationBuilder.CreateIndex(
name: "IX_OutboxMessage_ExpirationTime",
table: "OutboxMessage",
column: "ExpirationTime");
migrationBuilder.CreateIndex(
name: "IX_OutboxMessage_InboxMessageId_InboxConsumerId_SequenceNumber",
table: "OutboxMessage",
columns: new[] { "InboxMessageId", "InboxConsumerId", "SequenceNumber" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_OutboxMessage_OutboxId_SequenceNumber",
table: "OutboxMessage",
columns: new[] { "OutboxId", "SequenceNumber" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_OutboxState_Created",
table: "OutboxState",
column: "Created");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "OutboxMessage");
migrationBuilder.DropTable(
name: "InboxState");
migrationBuilder.DropTable(
name: "OutboxState");
}
}
}

View File

@@ -22,6 +22,174 @@ namespace TakeoutSaaS.Infrastructure.Migrations
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.InboxState", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<DateTime?>("Consumed")
.HasColumnType("timestamp with time zone");
b.Property<Guid>("ConsumerId")
.HasColumnType("uuid");
b.Property<DateTime?>("Delivered")
.HasColumnType("timestamp with time zone");
b.Property<DateTime?>("ExpirationTime")
.HasColumnType("timestamp with time zone");
b.Property<long?>("LastSequenceNumber")
.HasColumnType("bigint");
b.Property<Guid>("LockId")
.HasColumnType("uuid");
b.Property<Guid>("MessageId")
.HasColumnType("uuid");
b.Property<int>("ReceiveCount")
.HasColumnType("integer");
b.Property<DateTime>("Received")
.HasColumnType("timestamp with time zone");
b.Property<byte[]>("RowVersion")
.IsConcurrencyToken()
.ValueGeneratedOnAddOrUpdate()
.HasColumnType("bytea");
b.HasKey("Id");
b.HasIndex("Delivered");
b.ToTable("InboxState");
});
modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.OutboxMessage", b =>
{
b.Property<long>("SequenceNumber")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("SequenceNumber"));
b.Property<string>("Body")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ContentType")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<Guid?>("ConversationId")
.HasColumnType("uuid");
b.Property<Guid?>("CorrelationId")
.HasColumnType("uuid");
b.Property<string>("DestinationAddress")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<DateTime?>("EnqueueTime")
.HasColumnType("timestamp with time zone");
b.Property<DateTime?>("ExpirationTime")
.HasColumnType("timestamp with time zone");
b.Property<string>("FaultAddress")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("Headers")
.HasColumnType("text");
b.Property<Guid?>("InboxConsumerId")
.HasColumnType("uuid");
b.Property<Guid?>("InboxMessageId")
.HasColumnType("uuid");
b.Property<Guid?>("InitiatorId")
.HasColumnType("uuid");
b.Property<Guid>("MessageId")
.HasColumnType("uuid");
b.Property<string>("MessageType")
.IsRequired()
.HasColumnType("text");
b.Property<Guid?>("OutboxId")
.HasColumnType("uuid");
b.Property<string>("Properties")
.HasColumnType("text");
b.Property<Guid?>("RequestId")
.HasColumnType("uuid");
b.Property<string>("ResponseAddress")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<DateTime>("SentTime")
.HasColumnType("timestamp with time zone");
b.Property<string>("SourceAddress")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.HasKey("SequenceNumber");
b.HasIndex("EnqueueTime");
b.HasIndex("ExpirationTime");
b.HasIndex("OutboxId", "SequenceNumber")
.IsUnique();
b.HasIndex("InboxMessageId", "InboxConsumerId", "SequenceNumber")
.IsUnique();
b.ToTable("OutboxMessage");
});
modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.OutboxState", b =>
{
b.Property<Guid>("OutboxId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime>("Created")
.HasColumnType("timestamp with time zone");
b.Property<DateTime?>("Delivered")
.HasColumnType("timestamp with time zone");
b.Property<long?>("LastSequenceNumber")
.HasColumnType("bigint");
b.Property<Guid>("LockId")
.HasColumnType("uuid");
b.Property<byte[]>("RowVersion")
.IsConcurrencyToken()
.ValueGeneratedOnAddOrUpdate()
.HasColumnType("bytea");
b.HasKey("OutboxId");
b.HasIndex("Created");
b.ToTable("OutboxState");
});
modelBuilder.Entity("TakeoutSaaS.Domain.Analytics.Entities.MetricAlertRule", b =>
{
b.Property<long>("Id")
@@ -8667,6 +8835,18 @@ namespace TakeoutSaaS.Infrastructure.Migrations
});
});
modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.OutboxMessage", b =>
{
b.HasOne("MassTransit.EntityFrameworkCoreIntegration.OutboxState", null)
.WithMany()
.HasForeignKey("OutboxId");
b.HasOne("MassTransit.EntityFrameworkCoreIntegration.InboxState", null)
.WithMany()
.HasForeignKey("InboxMessageId", "InboxConsumerId")
.HasPrincipalKey("MessageId", "ConsumerId");
});
modelBuilder.Entity("TakeoutSaaS.Domain.Orders.Entities.OrderItem", b =>
{
b.HasOne("TakeoutSaaS.Domain.Orders.Entities.Order", null)