feat: migrate snowflake ids and refresh migrations

This commit is contained in:
2025-12-02 09:04:37 +08:00
parent 462e15abbb
commit 148475fa43
174 changed files with 8020 additions and 34278 deletions

View File

@@ -0,0 +1,48 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using TakeoutSaaS.Domain.Deliveries.Repositories;
using TakeoutSaaS.Domain.Merchants.Repositories;
using TakeoutSaaS.Domain.Orders.Repositories;
using TakeoutSaaS.Domain.Payments.Repositories;
using TakeoutSaaS.Domain.Products.Repositories;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Infrastructure.App.Options;
using TakeoutSaaS.Infrastructure.App.Persistence;
using TakeoutSaaS.Infrastructure.App.Repositories;
using TakeoutSaaS.Infrastructure.Common.Extensions;
using TakeoutSaaS.Shared.Abstractions.Constants;
namespace TakeoutSaaS.Infrastructure.App.Extensions;
/// <summary>
/// 业务主库基础设施注册扩展。
/// </summary>
public static class AppServiceCollectionExtensions
{
/// <summary>
/// 注册业务主库 DbContext 与仓储。
/// </summary>
/// <param name="services">服务集合。</param>
/// <param name="configuration">配置源。</param>
/// <returns>服务集合。</returns>
public static IServiceCollection AddAppInfrastructure(this IServiceCollection services, IConfiguration configuration)
{
services.AddDatabaseInfrastructure(configuration);
services.AddPostgresDbContext<TakeoutAppDbContext>(DatabaseConstants.AppDataSource);
services.AddScoped<IMerchantRepository, EfMerchantRepository>();
services.AddScoped<IStoreRepository, EfStoreRepository>();
services.AddScoped<IProductRepository, EfProductRepository>();
services.AddScoped<IOrderRepository, EfOrderRepository>();
services.AddScoped<IPaymentRepository, EfPaymentRepository>();
services.AddScoped<IDeliveryRepository, EfDeliveryRepository>();
services.AddOptions<AppSeedOptions>()
.Bind(configuration.GetSection(AppSeedOptions.SectionName))
.ValidateDataAnnotations();
services.AddHostedService<AppDataSeeder>();
return services;
}
}

View File

@@ -1,949 +0,0 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using TakeoutSaaS.Infrastructure.App.Persistence;
#nullable disable
namespace TakeoutSaaS.Infrastructure.App.Migrations
{
[DbContext(typeof(TakeoutAppDbContext))]
[Migration("20251201044927_InitialApp")]
partial class InitialApp
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("TakeoutSaaS.Domain.Deliveries.Entities.DeliveryOrder", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("CourierName")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("CourierPhone")
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uuid");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uuid");
b.Property<DateTime?>("DeliveredAt")
.HasColumnType("timestamp with time zone");
b.Property<decimal?>("DeliveryFee")
.HasPrecision(18, 2)
.HasColumnType("numeric(18,2)");
b.Property<DateTime?>("DispatchedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("FailureReason")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<Guid>("OrderId")
.HasColumnType("uuid");
b.Property<DateTime?>("PickedUpAt")
.HasColumnType("timestamp with time zone");
b.Property<int>("Provider")
.HasColumnType("integer");
b.Property<string>("ProviderOrderId")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<int>("Status")
.HasColumnType("integer");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("TenantId", "OrderId")
.IsUnique();
b.ToTable("delivery_orders", (string)null);
});
modelBuilder.Entity("TakeoutSaaS.Domain.Merchants.Entities.Merchant", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Address")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("BrandAlias")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("BrandName")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<string>("BusinessLicenseNumber")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("City")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("ContactEmail")
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<string>("ContactPhone")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uuid");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uuid");
b.Property<string>("District")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("LegalPerson")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<DateTime?>("OnboardedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Province")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("ReviewRemarks")
.HasMaxLength(512)
.HasColumnType("character varying(512)");
b.Property<int>("Status")
.HasColumnType("integer");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("TenantId");
b.ToTable("merchants", (string)null);
});
modelBuilder.Entity("TakeoutSaaS.Domain.Orders.Entities.Order", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("CancelReason")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<DateTime?>("CancelledAt")
.HasColumnType("timestamp with time zone");
b.Property<int>("Channel")
.HasColumnType("integer");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uuid");
b.Property<string>("CustomerName")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("CustomerPhone")
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uuid");
b.Property<int>("DeliveryType")
.HasColumnType("integer");
b.Property<decimal>("DiscountAmount")
.HasPrecision(18, 2)
.HasColumnType("numeric(18,2)");
b.Property<DateTime?>("FinishedAt")
.HasColumnType("timestamp with time zone");
b.Property<decimal>("ItemsAmount")
.HasPrecision(18, 2)
.HasColumnType("numeric(18,2)");
b.Property<string>("OrderNo")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<decimal>("PaidAmount")
.HasPrecision(18, 2)
.HasColumnType("numeric(18,2)");
b.Property<DateTime?>("PaidAt")
.HasColumnType("timestamp with time zone");
b.Property<decimal>("PayableAmount")
.HasPrecision(18, 2)
.HasColumnType("numeric(18,2)");
b.Property<int>("PaymentStatus")
.HasColumnType("integer");
b.Property<string>("QueueNumber")
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<string>("Remark")
.HasMaxLength(512)
.HasColumnType("character varying(512)");
b.Property<Guid?>("ReservationId")
.HasColumnType("uuid");
b.Property<int>("Status")
.HasColumnType("integer");
b.Property<Guid>("StoreId")
.HasColumnType("uuid");
b.Property<string>("TableNo")
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("TenantId", "OrderNo")
.IsUnique();
b.HasIndex("TenantId", "StoreId", "Status");
b.ToTable("orders", (string)null);
});
modelBuilder.Entity("TakeoutSaaS.Domain.Orders.Entities.OrderItem", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("AttributesJson")
.HasColumnType("text");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uuid");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uuid");
b.Property<decimal>("DiscountAmount")
.HasPrecision(18, 2)
.HasColumnType("numeric(18,2)");
b.Property<Guid>("OrderId")
.HasColumnType("uuid");
b.Property<Guid>("ProductId")
.HasColumnType("uuid");
b.Property<string>("ProductName")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<int>("Quantity")
.HasColumnType("integer");
b.Property<string>("SkuName")
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<decimal>("SubTotal")
.HasPrecision(18, 2)
.HasColumnType("numeric(18,2)");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<string>("Unit")
.HasMaxLength(16)
.HasColumnType("character varying(16)");
b.Property<decimal>("UnitPrice")
.HasPrecision(18, 2)
.HasColumnType("numeric(18,2)");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("OrderId");
b.HasIndex("TenantId", "OrderId");
b.ToTable("order_items", (string)null);
});
modelBuilder.Entity("TakeoutSaaS.Domain.Payments.Entities.PaymentRecord", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<decimal>("Amount")
.HasPrecision(18, 2)
.HasColumnType("numeric(18,2)");
b.Property<string>("ChannelTransactionId")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uuid");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uuid");
b.Property<int>("Method")
.HasColumnType("integer");
b.Property<Guid>("OrderId")
.HasColumnType("uuid");
b.Property<DateTime?>("PaidAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Payload")
.HasColumnType("text");
b.Property<string>("Remark")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<int>("Status")
.HasColumnType("integer");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<string>("TradeNo")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("TenantId", "OrderId");
b.ToTable("payment_records", (string)null);
});
modelBuilder.Entity("TakeoutSaaS.Domain.Products.Entities.Product", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid>("CategoryId")
.HasColumnType("uuid");
b.Property<string>("CoverImage")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uuid");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uuid");
b.Property<string>("Description")
.HasColumnType("text");
b.Property<bool>("EnableDelivery")
.HasColumnType("boolean");
b.Property<bool>("EnableDineIn")
.HasColumnType("boolean");
b.Property<bool>("EnablePickup")
.HasColumnType("boolean");
b.Property<string>("GalleryImages")
.HasMaxLength(1024)
.HasColumnType("character varying(1024)");
b.Property<bool>("IsFeatured")
.HasColumnType("boolean");
b.Property<int?>("MaxQuantityPerOrder")
.HasColumnType("integer");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<decimal?>("OriginalPrice")
.HasPrecision(18, 2)
.HasColumnType("numeric(18,2)");
b.Property<decimal>("Price")
.HasPrecision(18, 2)
.HasColumnType("numeric(18,2)");
b.Property<string>("SpuCode")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<int>("Status")
.HasColumnType("integer");
b.Property<int?>("StockQuantity")
.HasColumnType("integer");
b.Property<Guid>("StoreId")
.HasColumnType("uuid");
b.Property<string>("Subtitle")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<string>("Unit")
.HasMaxLength(16)
.HasColumnType("character varying(16)");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("TenantId", "SpuCode")
.IsUnique();
b.HasIndex("TenantId", "StoreId");
b.ToTable("products", (string)null);
});
modelBuilder.Entity("TakeoutSaaS.Domain.Products.Entities.ProductCategory", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uuid");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uuid");
b.Property<string>("Description")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<bool>("IsEnabled")
.HasColumnType("boolean");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<int>("SortOrder")
.HasColumnType("integer");
b.Property<Guid>("StoreId")
.HasColumnType("uuid");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("TenantId", "StoreId");
b.ToTable("product_categories", (string)null);
});
modelBuilder.Entity("TakeoutSaaS.Domain.Queues.Entities.QueueTicket", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime?>("CalledAt")
.HasColumnType("timestamp with time zone");
b.Property<DateTime?>("CancelledAt")
.HasColumnType("timestamp with time zone");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uuid");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uuid");
b.Property<int?>("EstimatedWaitMinutes")
.HasColumnType("integer");
b.Property<DateTime?>("ExpiredAt")
.HasColumnType("timestamp with time zone");
b.Property<int>("PartySize")
.HasColumnType("integer");
b.Property<string>("Remark")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<int>("Status")
.HasColumnType("integer");
b.Property<Guid>("StoreId")
.HasColumnType("uuid");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<string>("TicketNumber")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("TenantId", "StoreId");
b.HasIndex("TenantId", "StoreId", "TicketNumber")
.IsUnique();
b.ToTable("queue_tickets", (string)null);
});
modelBuilder.Entity("TakeoutSaaS.Domain.Reservations.Entities.Reservation", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime?>("CancelledAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("CheckInCode")
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<DateTime?>("CheckedInAt")
.HasColumnType("timestamp with time zone");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uuid");
b.Property<string>("CustomerName")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("CustomerPhone")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uuid");
b.Property<int>("PeopleCount")
.HasColumnType("integer");
b.Property<string>("Remark")
.HasMaxLength(512)
.HasColumnType("character varying(512)");
b.Property<string>("ReservationNo")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<DateTime>("ReservationTime")
.HasColumnType("timestamp with time zone");
b.Property<int>("Status")
.HasColumnType("integer");
b.Property<Guid>("StoreId")
.HasColumnType("uuid");
b.Property<string>("TablePreference")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("TenantId", "ReservationNo")
.IsUnique();
b.HasIndex("TenantId", "StoreId");
b.ToTable("reservations", (string)null);
});
modelBuilder.Entity("TakeoutSaaS.Domain.Stores.Entities.Store", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Address")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("Announcement")
.HasMaxLength(512)
.HasColumnType("character varying(512)");
b.Property<string>("BusinessHours")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("City")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uuid");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uuid");
b.Property<decimal>("DeliveryRadiusKm")
.HasPrecision(6, 2)
.HasColumnType("numeric(6,2)");
b.Property<string>("District")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<double?>("Latitude")
.HasColumnType("double precision");
b.Property<double?>("Longitude")
.HasColumnType("double precision");
b.Property<string>("ManagerName")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<Guid>("MerchantId")
.HasColumnType("uuid");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<string>("Phone")
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<string>("Province")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<bool>("QueueEnabled")
.HasColumnType("boolean");
b.Property<bool>("ReservationEnabled")
.HasColumnType("boolean");
b.Property<int>("Status")
.HasColumnType("integer");
b.Property<bool>("SupportsDelivery")
.HasColumnType("boolean");
b.Property<bool>("SupportsDineIn")
.HasColumnType("boolean");
b.Property<bool>("SupportsPickup")
.HasColumnType("boolean");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("MerchantId");
b.HasIndex("TenantId", "Code")
.IsUnique();
b.HasIndex("TenantId", "MerchantId");
b.ToTable("stores", (string)null);
});
modelBuilder.Entity("TakeoutSaaS.Domain.Tenants.Entities.Tenant", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("ContactEmail")
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<string>("ContactName")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("ContactPhone")
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uuid");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uuid");
b.Property<DateTime?>("EffectiveFrom")
.HasColumnType("timestamp with time zone");
b.Property<DateTime?>("EffectiveTo")
.HasColumnType("timestamp with time zone");
b.Property<string>("Industry")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("LogoUrl")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<string>("Remarks")
.HasMaxLength(512)
.HasColumnType("character varying(512)");
b.Property<string>("ShortName")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<int>("Status")
.HasColumnType("integer");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("Code")
.IsUnique();
b.ToTable("tenants", (string)null);
});
modelBuilder.Entity("TakeoutSaaS.Domain.Orders.Entities.OrderItem", b =>
{
b.HasOne("TakeoutSaaS.Domain.Orders.Entities.Order", null)
.WithMany()
.HasForeignKey("OrderId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("TakeoutSaaS.Domain.Stores.Entities.Store", b =>
{
b.HasOne("TakeoutSaaS.Domain.Merchants.Entities.Merchant", "Merchant")
.WithMany()
.HasForeignKey("MerchantId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Merchant");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -1,497 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace TakeoutSaaS.Infrastructure.App.Migrations
{
/// <inheritdoc />
public partial class InitialApp : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "delivery_orders",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
OrderId = table.Column<Guid>(type: "uuid", nullable: false),
Provider = table.Column<int>(type: "integer", nullable: false),
ProviderOrderId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
Status = table.Column<int>(type: "integer", nullable: false),
DeliveryFee = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: true),
CourierName = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
CourierPhone = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
DispatchedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
PickedUpAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
DeliveredAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
FailureReason = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CreatedBy = table.Column<Guid>(type: "uuid", nullable: true),
UpdatedBy = table.Column<Guid>(type: "uuid", nullable: true),
DeletedBy = table.Column<Guid>(type: "uuid", nullable: true),
TenantId = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_delivery_orders", x => x.Id);
});
migrationBuilder.CreateTable(
name: "merchants",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
BrandName = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
BrandAlias = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
LegalPerson = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
BusinessLicenseNumber = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
ContactPhone = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
ContactEmail = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true),
Province = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
City = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
District = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
Address = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
Status = table.Column<int>(type: "integer", nullable: false),
ReviewRemarks = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: true),
OnboardedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CreatedBy = table.Column<Guid>(type: "uuid", nullable: true),
UpdatedBy = table.Column<Guid>(type: "uuid", nullable: true),
DeletedBy = table.Column<Guid>(type: "uuid", nullable: true),
TenantId = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_merchants", x => x.Id);
});
migrationBuilder.CreateTable(
name: "orders",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
OrderNo = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
StoreId = table.Column<Guid>(type: "uuid", nullable: false),
Channel = table.Column<int>(type: "integer", nullable: false),
DeliveryType = table.Column<int>(type: "integer", nullable: false),
Status = table.Column<int>(type: "integer", nullable: false),
PaymentStatus = table.Column<int>(type: "integer", nullable: false),
CustomerName = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
CustomerPhone = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
TableNo = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
QueueNumber = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
ReservationId = table.Column<Guid>(type: "uuid", nullable: true),
ItemsAmount = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
DiscountAmount = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
PayableAmount = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
PaidAmount = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
PaidAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
FinishedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CancelledAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CancelReason = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
Remark = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: true),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CreatedBy = table.Column<Guid>(type: "uuid", nullable: true),
UpdatedBy = table.Column<Guid>(type: "uuid", nullable: true),
DeletedBy = table.Column<Guid>(type: "uuid", nullable: true),
TenantId = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_orders", x => x.Id);
});
migrationBuilder.CreateTable(
name: "payment_records",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
OrderId = table.Column<Guid>(type: "uuid", nullable: false),
Method = table.Column<int>(type: "integer", nullable: false),
Status = table.Column<int>(type: "integer", nullable: false),
Amount = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
TradeNo = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
ChannelTransactionId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
PaidAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
Remark = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
Payload = table.Column<string>(type: "text", nullable: true),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CreatedBy = table.Column<Guid>(type: "uuid", nullable: true),
UpdatedBy = table.Column<Guid>(type: "uuid", nullable: true),
DeletedBy = table.Column<Guid>(type: "uuid", nullable: true),
TenantId = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_payment_records", x => x.Id);
});
migrationBuilder.CreateTable(
name: "product_categories",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
StoreId = table.Column<Guid>(type: "uuid", nullable: false),
Name = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
Description = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
SortOrder = table.Column<int>(type: "integer", nullable: false),
IsEnabled = table.Column<bool>(type: "boolean", nullable: false),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CreatedBy = table.Column<Guid>(type: "uuid", nullable: true),
UpdatedBy = table.Column<Guid>(type: "uuid", nullable: true),
DeletedBy = table.Column<Guid>(type: "uuid", nullable: true),
TenantId = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_product_categories", x => x.Id);
});
migrationBuilder.CreateTable(
name: "products",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
StoreId = table.Column<Guid>(type: "uuid", nullable: false),
CategoryId = table.Column<Guid>(type: "uuid", nullable: false),
SpuCode = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
Name = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
Subtitle = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
Unit = table.Column<string>(type: "character varying(16)", maxLength: 16, nullable: true),
Price = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
OriginalPrice = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: true),
StockQuantity = table.Column<int>(type: "integer", nullable: true),
MaxQuantityPerOrder = table.Column<int>(type: "integer", nullable: true),
Status = table.Column<int>(type: "integer", nullable: false),
CoverImage = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
GalleryImages = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
Description = table.Column<string>(type: "text", nullable: true),
EnableDineIn = table.Column<bool>(type: "boolean", nullable: false),
EnablePickup = table.Column<bool>(type: "boolean", nullable: false),
EnableDelivery = table.Column<bool>(type: "boolean", nullable: false),
IsFeatured = table.Column<bool>(type: "boolean", nullable: false),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CreatedBy = table.Column<Guid>(type: "uuid", nullable: true),
UpdatedBy = table.Column<Guid>(type: "uuid", nullable: true),
DeletedBy = table.Column<Guid>(type: "uuid", nullable: true),
TenantId = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_products", x => x.Id);
});
migrationBuilder.CreateTable(
name: "queue_tickets",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
StoreId = table.Column<Guid>(type: "uuid", nullable: false),
TicketNumber = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
PartySize = table.Column<int>(type: "integer", nullable: false),
Status = table.Column<int>(type: "integer", nullable: false),
EstimatedWaitMinutes = table.Column<int>(type: "integer", nullable: true),
CalledAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
ExpiredAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CancelledAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
Remark = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CreatedBy = table.Column<Guid>(type: "uuid", nullable: true),
UpdatedBy = table.Column<Guid>(type: "uuid", nullable: true),
DeletedBy = table.Column<Guid>(type: "uuid", nullable: true),
TenantId = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_queue_tickets", x => x.Id);
});
migrationBuilder.CreateTable(
name: "reservations",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
StoreId = table.Column<Guid>(type: "uuid", nullable: false),
ReservationNo = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
CustomerName = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
CustomerPhone = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
PeopleCount = table.Column<int>(type: "integer", nullable: false),
ReservationTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
Status = table.Column<int>(type: "integer", nullable: false),
TablePreference = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
Remark = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: true),
CheckInCode = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
CheckedInAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CancelledAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CreatedBy = table.Column<Guid>(type: "uuid", nullable: true),
UpdatedBy = table.Column<Guid>(type: "uuid", nullable: true),
DeletedBy = table.Column<Guid>(type: "uuid", nullable: true),
TenantId = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_reservations", x => x.Id);
});
migrationBuilder.CreateTable(
name: "tenants",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
Code = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
Name = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
ShortName = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
ContactName = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
ContactPhone = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
ContactEmail = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true),
Industry = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
LogoUrl = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
EffectiveFrom = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
EffectiveTo = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
Status = table.Column<int>(type: "integer", nullable: false),
Remarks = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: true),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CreatedBy = table.Column<Guid>(type: "uuid", nullable: true),
UpdatedBy = table.Column<Guid>(type: "uuid", nullable: true),
DeletedBy = table.Column<Guid>(type: "uuid", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_tenants", x => x.Id);
});
migrationBuilder.CreateTable(
name: "stores",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
MerchantId = table.Column<Guid>(type: "uuid", nullable: false),
Code = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
Name = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
Phone = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
ManagerName = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
Status = table.Column<int>(type: "integer", nullable: false),
Province = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
City = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
District = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
Address = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
Longitude = table.Column<double>(type: "double precision", nullable: true),
Latitude = table.Column<double>(type: "double precision", nullable: true),
BusinessHours = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
SupportsDineIn = table.Column<bool>(type: "boolean", nullable: false),
SupportsPickup = table.Column<bool>(type: "boolean", nullable: false),
SupportsDelivery = table.Column<bool>(type: "boolean", nullable: false),
DeliveryRadiusKm = table.Column<decimal>(type: "numeric(6,2)", precision: 6, scale: 2, nullable: false),
QueueEnabled = table.Column<bool>(type: "boolean", nullable: false),
ReservationEnabled = table.Column<bool>(type: "boolean", nullable: false),
Announcement = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: true),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CreatedBy = table.Column<Guid>(type: "uuid", nullable: true),
UpdatedBy = table.Column<Guid>(type: "uuid", nullable: true),
DeletedBy = table.Column<Guid>(type: "uuid", nullable: true),
TenantId = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_stores", x => x.Id);
table.ForeignKey(
name: "FK_stores_merchants_MerchantId",
column: x => x.MerchantId,
principalTable: "merchants",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "order_items",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
OrderId = table.Column<Guid>(type: "uuid", nullable: false),
ProductId = table.Column<Guid>(type: "uuid", nullable: false),
ProductName = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
SkuName = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true),
Unit = table.Column<string>(type: "character varying(16)", maxLength: 16, nullable: true),
Quantity = table.Column<int>(type: "integer", nullable: false),
UnitPrice = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
DiscountAmount = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
SubTotal = table.Column<decimal>(type: "numeric(18,2)", precision: 18, scale: 2, nullable: false),
AttributesJson = table.Column<string>(type: "text", nullable: true),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CreatedBy = table.Column<Guid>(type: "uuid", nullable: true),
UpdatedBy = table.Column<Guid>(type: "uuid", nullable: true),
DeletedBy = table.Column<Guid>(type: "uuid", nullable: true),
TenantId = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_order_items", x => x.Id);
table.ForeignKey(
name: "FK_order_items_orders_OrderId",
column: x => x.OrderId,
principalTable: "orders",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_delivery_orders_TenantId_OrderId",
table: "delivery_orders",
columns: new[] { "TenantId", "OrderId" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_merchants_TenantId",
table: "merchants",
column: "TenantId");
migrationBuilder.CreateIndex(
name: "IX_order_items_OrderId",
table: "order_items",
column: "OrderId");
migrationBuilder.CreateIndex(
name: "IX_order_items_TenantId_OrderId",
table: "order_items",
columns: new[] { "TenantId", "OrderId" });
migrationBuilder.CreateIndex(
name: "IX_orders_TenantId_OrderNo",
table: "orders",
columns: new[] { "TenantId", "OrderNo" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_orders_TenantId_StoreId_Status",
table: "orders",
columns: new[] { "TenantId", "StoreId", "Status" });
migrationBuilder.CreateIndex(
name: "IX_payment_records_TenantId_OrderId",
table: "payment_records",
columns: new[] { "TenantId", "OrderId" });
migrationBuilder.CreateIndex(
name: "IX_product_categories_TenantId_StoreId",
table: "product_categories",
columns: new[] { "TenantId", "StoreId" });
migrationBuilder.CreateIndex(
name: "IX_products_TenantId_SpuCode",
table: "products",
columns: new[] { "TenantId", "SpuCode" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_products_TenantId_StoreId",
table: "products",
columns: new[] { "TenantId", "StoreId" });
migrationBuilder.CreateIndex(
name: "IX_queue_tickets_TenantId_StoreId",
table: "queue_tickets",
columns: new[] { "TenantId", "StoreId" });
migrationBuilder.CreateIndex(
name: "IX_queue_tickets_TenantId_StoreId_TicketNumber",
table: "queue_tickets",
columns: new[] { "TenantId", "StoreId", "TicketNumber" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_reservations_TenantId_ReservationNo",
table: "reservations",
columns: new[] { "TenantId", "ReservationNo" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_reservations_TenantId_StoreId",
table: "reservations",
columns: new[] { "TenantId", "StoreId" });
migrationBuilder.CreateIndex(
name: "IX_stores_MerchantId",
table: "stores",
column: "MerchantId");
migrationBuilder.CreateIndex(
name: "IX_stores_TenantId_Code",
table: "stores",
columns: new[] { "TenantId", "Code" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_stores_TenantId_MerchantId",
table: "stores",
columns: new[] { "TenantId", "MerchantId" });
migrationBuilder.CreateIndex(
name: "IX_tenants_Code",
table: "tenants",
column: "Code",
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "delivery_orders");
migrationBuilder.DropTable(
name: "order_items");
migrationBuilder.DropTable(
name: "payment_records");
migrationBuilder.DropTable(
name: "product_categories");
migrationBuilder.DropTable(
name: "products");
migrationBuilder.DropTable(
name: "queue_tickets");
migrationBuilder.DropTable(
name: "reservations");
migrationBuilder.DropTable(
name: "stores");
migrationBuilder.DropTable(
name: "tenants");
migrationBuilder.DropTable(
name: "orders");
migrationBuilder.DropTable(
name: "merchants");
}
}
}

View File

@@ -0,0 +1,29 @@
using System.Collections.Generic;
namespace TakeoutSaaS.Infrastructure.App.Options;
/// <summary>
/// 业务数据种子配置。
/// </summary>
public sealed class AppSeedOptions
{
/// <summary>
/// 配置节名称。
/// </summary>
public const string SectionName = "App:Seed";
/// <summary>
/// 是否启用业务数据种子。
/// </summary>
public bool Enabled { get; set; }
/// <summary>
/// 默认租户配置。
/// </summary>
public TenantSeedOptions? DefaultTenant { get; set; }
/// <summary>
/// 基础字典分组。
/// </summary>
public List<DictionarySeedGroupOptions> DictionaryGroups { get; set; } = new();
}

View File

@@ -0,0 +1,51 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using TakeoutSaaS.Domain.Dictionary.Enums;
namespace TakeoutSaaS.Infrastructure.App.Options;
/// <summary>
/// 字典分组种子配置。
/// </summary>
public sealed class DictionarySeedGroupOptions
{
/// <summary>
/// 所属租户,不填则使用默认租户或系统租户。
/// </summary>
public long? TenantId { get; set; }
/// <summary>
/// 分组编码。
/// </summary>
[Required]
[MaxLength(64)]
public string Code { get; set; } = string.Empty;
/// <summary>
/// 分组名称。
/// </summary>
[Required]
[MaxLength(128)]
public string Name { get; set; } = string.Empty;
/// <summary>
/// 分组作用域。
/// </summary>
public DictionaryScope Scope { get; set; } = DictionaryScope.Business;
/// <summary>
/// 描述信息。
/// </summary>
[MaxLength(512)]
public string? Description { get; set; }
/// <summary>
/// 是否启用。
/// </summary>
public bool IsEnabled { get; set; } = true;
/// <summary>
/// 字典项集合。
/// </summary>
public List<DictionarySeedItemOptions> Items { get; set; } = new();
}

View File

@@ -0,0 +1,39 @@
using System.ComponentModel.DataAnnotations;
namespace TakeoutSaaS.Infrastructure.App.Options;
/// <summary>
/// 字典项种子配置。
/// </summary>
public sealed class DictionarySeedItemOptions
{
/// <summary>
/// 字典项键。
/// </summary>
[Required]
[MaxLength(64)]
public string Key { get; set; } = string.Empty;
/// <summary>
/// 字典项值。
/// </summary>
[Required]
[MaxLength(256)]
public string Value { get; set; } = string.Empty;
/// <summary>
/// 描述。
/// </summary>
[MaxLength(512)]
public string? Description { get; set; }
/// <summary>
/// 排序。
/// </summary>
public int SortOrder { get; set; } = 100;
/// <summary>
/// 是否启用。
/// </summary>
public bool IsEnabled { get; set; } = true;
}

View File

@@ -0,0 +1,46 @@
using System.ComponentModel.DataAnnotations;
namespace TakeoutSaaS.Infrastructure.App.Options;
/// <summary>
/// 默认租户种子配置。
/// </summary>
public sealed class TenantSeedOptions
{
/// <summary>
/// 自定义租户标识,不填则自动生成。
/// </summary>
public long TenantId { get; set; }
/// <summary>
/// 租户编码。
/// </summary>
[Required]
[MaxLength(64)]
public string Code { get; set; } = string.Empty;
/// <summary>
/// 租户名称。
/// </summary>
[Required]
[MaxLength(128)]
public string Name { get; set; } = string.Empty;
/// <summary>
/// 租户简称。
/// </summary>
[MaxLength(128)]
public string? ShortName { get; set; }
/// <summary>
/// 联系人姓名。
/// </summary>
[MaxLength(64)]
public string? ContactName { get; set; }
/// <summary>
/// 联系电话。
/// </summary>
[MaxLength(32)]
public string? ContactPhone { get; set; }
}

View File

@@ -0,0 +1,301 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using TakeoutSaaS.Domain.Dictionary.Entities;
using TakeoutSaaS.Domain.Tenants.Entities;
using TakeoutSaaS.Domain.Tenants.Enums;
using TakeoutSaaS.Infrastructure.App.Options;
using TakeoutSaaS.Infrastructure.Dictionary.Persistence;
namespace TakeoutSaaS.Infrastructure.App.Persistence;
/// <summary>
/// 业务数据种子,确保默认租户与基础字典可重复执行。
/// </summary>
public sealed class AppDataSeeder : IHostedService
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<AppDataSeeder> _logger;
private readonly AppSeedOptions _options;
/// <summary>
/// 初始化种子服务。
/// </summary>
public AppDataSeeder(
IServiceProvider serviceProvider,
ILogger<AppDataSeeder> logger,
IOptions<AppSeedOptions> options)
{
_serviceProvider = serviceProvider;
_logger = logger;
_options = options.Value;
}
/// <inheritdoc />
public async Task StartAsync(CancellationToken cancellationToken)
{
if (!_options.Enabled)
{
_logger.LogInformation("AppSeed 未启用,跳过业务数据初始化");
return;
}
using var scope = _serviceProvider.CreateScope();
var appDbContext = scope.ServiceProvider.GetRequiredService<TakeoutAppDbContext>();
var dictionaryDbContext = scope.ServiceProvider.GetRequiredService<DictionaryDbContext>();
var defaultTenantId = await EnsureDefaultTenantAsync(appDbContext, cancellationToken);
await EnsureDictionarySeedsAsync(dictionaryDbContext, defaultTenantId, cancellationToken);
_logger.LogInformation("AppSeed 完成业务数据初始化");
}
/// <inheritdoc />
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
/// <summary>
/// 确保默认租户存在。
/// </summary>
private async Task<long?> EnsureDefaultTenantAsync(TakeoutAppDbContext dbContext, CancellationToken cancellationToken)
{
var tenantOptions = _options.DefaultTenant;
if (tenantOptions == null || string.IsNullOrWhiteSpace(tenantOptions.Code) || string.IsNullOrWhiteSpace(tenantOptions.Name))
{
_logger.LogInformation("AppSeed 未配置默认租户,跳过租户种子");
return null;
}
var code = tenantOptions.Code.Trim();
var existingTenant = await dbContext.Tenants
.IgnoreQueryFilters()
.FirstOrDefaultAsync(x => x.Code == code, cancellationToken);
if (existingTenant == null)
{
var tenant = new Tenant
{
Id = tenantOptions.TenantId,
Code = code,
Name = tenantOptions.Name.Trim(),
ShortName = tenantOptions.ShortName?.Trim(),
ContactName = tenantOptions.ContactName?.Trim(),
ContactPhone = tenantOptions.ContactPhone?.Trim(),
Status = TenantStatus.Active
};
await dbContext.Tenants.AddAsync(tenant, cancellationToken);
await dbContext.SaveChangesAsync(cancellationToken);
_logger.LogInformation("AppSeed 已创建默认租户 {TenantCode}", code);
return tenant.Id;
}
var updated = false;
if (!string.Equals(existingTenant.Name, tenantOptions.Name, StringComparison.Ordinal))
{
existingTenant.Name = tenantOptions.Name.Trim();
updated = true;
}
if (!string.Equals(existingTenant.ShortName, tenantOptions.ShortName, StringComparison.Ordinal))
{
existingTenant.ShortName = tenantOptions.ShortName?.Trim();
updated = true;
}
if (!string.Equals(existingTenant.ContactName, tenantOptions.ContactName, StringComparison.Ordinal))
{
existingTenant.ContactName = tenantOptions.ContactName?.Trim();
updated = true;
}
if (!string.Equals(existingTenant.ContactPhone, tenantOptions.ContactPhone, StringComparison.Ordinal))
{
existingTenant.ContactPhone = tenantOptions.ContactPhone?.Trim();
updated = true;
}
if (existingTenant.Status != TenantStatus.Active)
{
existingTenant.Status = TenantStatus.Active;
updated = true;
}
if (updated)
{
dbContext.Tenants.Update(existingTenant);
await dbContext.SaveChangesAsync(cancellationToken);
_logger.LogInformation("AppSeed 已更新默认租户 {TenantCode}", code);
}
else
{
_logger.LogInformation("AppSeed 默认租户 {TenantCode} 已存在且无需更新", code);
}
return existingTenant.Id;
}
/// <summary>
/// 确保基础字典存在。
/// </summary>
private async Task EnsureDictionarySeedsAsync(DictionaryDbContext dbContext, long? defaultTenantId, CancellationToken cancellationToken)
{
if (_options.DictionaryGroups == null || _options.DictionaryGroups.Count == 0)
{
_logger.LogInformation("AppSeed 未配置基础字典,跳过字典种子");
return;
}
foreach (var groupOptions in _options.DictionaryGroups)
{
if (string.IsNullOrWhiteSpace(groupOptions.Code) || string.IsNullOrWhiteSpace(groupOptions.Name))
{
_logger.LogWarning("AppSeed 跳过字典分组Code 或 Name 为空");
continue;
}
var tenantId = groupOptions.TenantId ?? defaultTenantId ?? 0;
var code = groupOptions.Code.Trim();
var group = await dbContext.DictionaryGroups
.IgnoreQueryFilters()
.FirstOrDefaultAsync(x => x.TenantId == tenantId && x.Code == code, cancellationToken);
if (group == null)
{
group = new DictionaryGroup
{
Id = 0,
TenantId = tenantId,
Code = code,
Name = groupOptions.Name.Trim(),
Scope = groupOptions.Scope,
Description = groupOptions.Description?.Trim(),
IsEnabled = groupOptions.IsEnabled
};
await dbContext.DictionaryGroups.AddAsync(group, cancellationToken);
_logger.LogInformation("AppSeed 创建字典分组 {GroupCode} (Tenant: {TenantId})", code, tenantId);
}
else
{
var groupUpdated = false;
if (!string.Equals(group.Name, groupOptions.Name, StringComparison.Ordinal))
{
group.Name = groupOptions.Name.Trim();
groupUpdated = true;
}
if (!string.Equals(group.Description, groupOptions.Description, StringComparison.Ordinal))
{
group.Description = groupOptions.Description?.Trim();
groupUpdated = true;
}
if (group.Scope != groupOptions.Scope)
{
group.Scope = groupOptions.Scope;
groupUpdated = true;
}
if (group.IsEnabled != groupOptions.IsEnabled)
{
group.IsEnabled = groupOptions.IsEnabled;
groupUpdated = true;
}
if (groupUpdated)
{
dbContext.DictionaryGroups.Update(group);
}
}
await UpsertDictionaryItemsAsync(dbContext, group, groupOptions.Items, tenantId, cancellationToken);
}
await dbContext.SaveChangesAsync(cancellationToken);
}
/// <summary>
/// 合并字典项。
/// </summary>
private static async Task UpsertDictionaryItemsAsync(
DictionaryDbContext dbContext,
DictionaryGroup group,
IEnumerable<DictionarySeedItemOptions> seedItems,
long tenantId,
CancellationToken cancellationToken)
{
var materializedItems = seedItems
.Where(item => !string.IsNullOrWhiteSpace(item.Key) && !string.IsNullOrWhiteSpace(item.Value))
.ToList();
if (materializedItems.Count == 0)
{
return;
}
var existingItems = await dbContext.DictionaryItems
.IgnoreQueryFilters()
.Where(x => x.GroupId == group.Id)
.ToListAsync(cancellationToken);
foreach (var seed in materializedItems)
{
var key = seed.Key.Trim();
var existing = existingItems.FirstOrDefault(x => x.Key.Equals(key, StringComparison.OrdinalIgnoreCase));
if (existing == null)
{
var newItem = new DictionaryItem
{
Id = 0,
TenantId = tenantId,
GroupId = group.Id,
Key = key,
Value = seed.Value.Trim(),
Description = seed.Description?.Trim(),
SortOrder = seed.SortOrder,
IsEnabled = seed.IsEnabled
};
await dbContext.DictionaryItems.AddAsync(newItem, cancellationToken);
continue;
}
var updated = false;
if (!string.Equals(existing.Value, seed.Value, StringComparison.Ordinal))
{
existing.Value = seed.Value.Trim();
updated = true;
}
if (!string.Equals(existing.Description, seed.Description, StringComparison.Ordinal))
{
existing.Description = seed.Description?.Trim();
updated = true;
}
if (existing.SortOrder != seed.SortOrder)
{
existing.SortOrder = seed.SortOrder;
updated = true;
}
if (existing.IsEnabled != seed.IsEnabled)
{
existing.IsEnabled = seed.IsEnabled;
updated = true;
}
if (updated)
{
dbContext.DictionaryItems.Update(existing);
}
}
}
}

View File

@@ -20,6 +20,7 @@ using TakeoutSaaS.Domain.Reservations.Entities;
using TakeoutSaaS.Domain.Stores.Entities;
using TakeoutSaaS.Domain.Tenants.Entities;
using TakeoutSaaS.Infrastructure.Common.Persistence;
using TakeoutSaaS.Shared.Abstractions.Ids;
using TakeoutSaaS.Shared.Abstractions.Security;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
@@ -31,8 +32,9 @@ namespace TakeoutSaaS.Infrastructure.App.Persistence;
public sealed class TakeoutAppDbContext(
DbContextOptions<TakeoutAppDbContext> options,
ITenantProvider tenantProvider,
ICurrentUserAccessor? currentUserAccessor = null)
: TenantAwareDbContext(options, tenantProvider, currentUserAccessor)
ICurrentUserAccessor? currentUserAccessor = null,
IIdGenerator? idGenerator = null)
: TenantAwareDbContext(options, tenantProvider, currentUserAccessor, idGenerator)
{
public DbSet<Tenant> Tenants => Set<Tenant>();
public DbSet<TenantPackage> TenantPackages => Set<TenantPackage>();

View File

@@ -0,0 +1,71 @@
using System.Linq;
using Microsoft.EntityFrameworkCore;
using TakeoutSaaS.Domain.Deliveries.Entities;
using TakeoutSaaS.Domain.Deliveries.Repositories;
using TakeoutSaaS.Infrastructure.App.Persistence;
namespace TakeoutSaaS.Infrastructure.App.Repositories;
/// <summary>
/// 配送聚合的 EF Core 仓储实现。
/// </summary>
public sealed class EfDeliveryRepository : IDeliveryRepository
{
private readonly TakeoutAppDbContext _context;
/// <summary>
/// 初始化仓储。
/// </summary>
public EfDeliveryRepository(TakeoutAppDbContext context)
{
_context = context;
}
/// <inheritdoc />
public Task<DeliveryOrder?> FindByIdAsync(long deliveryOrderId, long tenantId, CancellationToken cancellationToken = default)
{
return _context.DeliveryOrders
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.Id == deliveryOrderId)
.FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc />
public Task<DeliveryOrder?> FindByOrderIdAsync(long orderId, long tenantId, CancellationToken cancellationToken = default)
{
return _context.DeliveryOrders
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.OrderId == orderId)
.FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc />
public async Task<IReadOnlyList<DeliveryEvent>> GetEventsAsync(long deliveryOrderId, long tenantId, CancellationToken cancellationToken = default)
{
var events = await _context.DeliveryEvents
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.DeliveryOrderId == deliveryOrderId)
.OrderBy(x => x.CreatedAt)
.ToListAsync(cancellationToken);
return events;
}
/// <inheritdoc />
public Task AddDeliveryOrderAsync(DeliveryOrder deliveryOrder, CancellationToken cancellationToken = default)
{
return _context.DeliveryOrders.AddAsync(deliveryOrder, cancellationToken).AsTask();
}
/// <inheritdoc />
public Task AddEventAsync(DeliveryEvent deliveryEvent, CancellationToken cancellationToken = default)
{
return _context.DeliveryEvents.AddAsync(deliveryEvent, cancellationToken).AsTask();
}
/// <inheritdoc />
public Task SaveChangesAsync(CancellationToken cancellationToken = default)
{
return _context.SaveChangesAsync(cancellationToken);
}
}

View File

@@ -0,0 +1,116 @@
using System.Linq;
using Microsoft.EntityFrameworkCore;
using TakeoutSaaS.Domain.Merchants.Entities;
using TakeoutSaaS.Domain.Merchants.Enums;
using TakeoutSaaS.Domain.Merchants.Repositories;
using TakeoutSaaS.Infrastructure.App.Persistence;
namespace TakeoutSaaS.Infrastructure.App.Repositories;
/// <summary>
/// 商户聚合的 EF Core 仓储实现。
/// </summary>
public sealed class EfMerchantRepository : IMerchantRepository
{
private readonly TakeoutAppDbContext _context;
/// <summary>
/// 初始化仓储。
/// </summary>
public EfMerchantRepository(TakeoutAppDbContext context)
{
_context = context;
}
/// <inheritdoc />
public Task<Merchant?> FindByIdAsync(long merchantId, long tenantId, CancellationToken cancellationToken = default)
{
return _context.Merchants
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.Id == merchantId)
.FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc />
public async Task<IReadOnlyList<Merchant>> SearchAsync(long tenantId, MerchantStatus? status, CancellationToken cancellationToken = default)
{
var query = _context.Merchants
.AsNoTracking()
.Where(x => x.TenantId == tenantId);
if (status.HasValue)
{
query = query.Where(x => x.Status == status.Value);
}
return await query
.OrderByDescending(x => x.CreatedAt)
.ToListAsync(cancellationToken);
}
/// <inheritdoc />
public async Task<IReadOnlyList<MerchantStaff>> GetStaffAsync(long merchantId, long tenantId, CancellationToken cancellationToken = default)
{
var staffs = await _context.MerchantStaff
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.MerchantId == merchantId)
.OrderBy(x => x.Name)
.ToListAsync(cancellationToken);
return staffs;
}
/// <inheritdoc />
public async Task<IReadOnlyList<MerchantContract>> GetContractsAsync(long merchantId, long tenantId, CancellationToken cancellationToken = default)
{
var contracts = await _context.MerchantContracts
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.MerchantId == merchantId)
.OrderByDescending(x => x.CreatedAt)
.ToListAsync(cancellationToken);
return contracts;
}
/// <inheritdoc />
public async Task<IReadOnlyList<MerchantDocument>> GetDocumentsAsync(long merchantId, long tenantId, CancellationToken cancellationToken = default)
{
var documents = await _context.MerchantDocuments
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.MerchantId == merchantId)
.OrderBy(x => x.CreatedAt)
.ToListAsync(cancellationToken);
return documents;
}
/// <inheritdoc />
public Task AddMerchantAsync(Merchant merchant, CancellationToken cancellationToken = default)
{
return _context.Merchants.AddAsync(merchant, cancellationToken).AsTask();
}
/// <inheritdoc />
public Task AddStaffAsync(MerchantStaff staff, CancellationToken cancellationToken = default)
{
return _context.MerchantStaff.AddAsync(staff, cancellationToken).AsTask();
}
/// <inheritdoc />
public Task AddContractAsync(MerchantContract contract, CancellationToken cancellationToken = default)
{
return _context.MerchantContracts.AddAsync(contract, cancellationToken).AsTask();
}
/// <inheritdoc />
public Task AddDocumentAsync(MerchantDocument document, CancellationToken cancellationToken = default)
{
return _context.MerchantDocuments.AddAsync(document, cancellationToken).AsTask();
}
/// <inheritdoc />
public Task SaveChangesAsync(CancellationToken cancellationToken = default)
{
return _context.SaveChangesAsync(cancellationToken);
}
}

View File

@@ -0,0 +1,133 @@
using System.Linq;
using Microsoft.EntityFrameworkCore;
using TakeoutSaaS.Domain.Orders.Entities;
using TakeoutSaaS.Domain.Orders.Enums;
using TakeoutSaaS.Domain.Orders.Repositories;
using TakeoutSaaS.Domain.Payments.Enums;
using TakeoutSaaS.Infrastructure.App.Persistence;
namespace TakeoutSaaS.Infrastructure.App.Repositories;
/// <summary>
/// 订单聚合的 EF Core 仓储实现。
/// </summary>
public sealed class EfOrderRepository : IOrderRepository
{
private readonly TakeoutAppDbContext _context;
/// <summary>
/// 初始化仓储。
/// </summary>
public EfOrderRepository(TakeoutAppDbContext context)
{
_context = context;
}
/// <inheritdoc />
public Task<Order?> FindByIdAsync(long orderId, long tenantId, CancellationToken cancellationToken = default)
{
return _context.Orders
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.Id == orderId)
.FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc />
public Task<Order?> FindByOrderNoAsync(string orderNo, long tenantId, CancellationToken cancellationToken = default)
{
return _context.Orders
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.OrderNo == orderNo)
.FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc />
public async Task<IReadOnlyList<Order>> SearchAsync(long tenantId, OrderStatus? status, PaymentStatus? paymentStatus, CancellationToken cancellationToken = default)
{
var query = _context.Orders
.AsNoTracking()
.Where(x => x.TenantId == tenantId);
if (status.HasValue)
{
query = query.Where(x => x.Status == status.Value);
}
if (paymentStatus.HasValue)
{
query = query.Where(x => x.PaymentStatus == paymentStatus.Value);
}
var orders = await query
.OrderByDescending(x => x.CreatedAt)
.ToListAsync(cancellationToken);
return orders;
}
/// <inheritdoc />
public async Task<IReadOnlyList<OrderItem>> GetItemsAsync(long orderId, long tenantId, CancellationToken cancellationToken = default)
{
var items = await _context.OrderItems
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.OrderId == orderId)
.OrderBy(x => x.Id)
.ToListAsync(cancellationToken);
return items;
}
/// <inheritdoc />
public async Task<IReadOnlyList<OrderStatusHistory>> GetStatusHistoryAsync(long orderId, long tenantId, CancellationToken cancellationToken = default)
{
var histories = await _context.OrderStatusHistories
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.OrderId == orderId)
.OrderBy(x => x.CreatedAt)
.ToListAsync(cancellationToken);
return histories;
}
/// <inheritdoc />
public async Task<IReadOnlyList<RefundRequest>> GetRefundsAsync(long orderId, long tenantId, CancellationToken cancellationToken = default)
{
var refunds = await _context.RefundRequests
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.OrderId == orderId)
.OrderByDescending(x => x.CreatedAt)
.ToListAsync(cancellationToken);
return refunds;
}
/// <inheritdoc />
public Task AddOrderAsync(Order order, CancellationToken cancellationToken = default)
{
return _context.Orders.AddAsync(order, cancellationToken).AsTask();
}
/// <inheritdoc />
public Task AddItemsAsync(IEnumerable<OrderItem> items, CancellationToken cancellationToken = default)
{
return _context.OrderItems.AddRangeAsync(items, cancellationToken);
}
/// <inheritdoc />
public Task AddStatusHistoryAsync(OrderStatusHistory history, CancellationToken cancellationToken = default)
{
return _context.OrderStatusHistories.AddAsync(history, cancellationToken).AsTask();
}
/// <inheritdoc />
public Task AddRefundAsync(RefundRequest refund, CancellationToken cancellationToken = default)
{
return _context.RefundRequests.AddAsync(refund, cancellationToken).AsTask();
}
/// <inheritdoc />
public Task SaveChangesAsync(CancellationToken cancellationToken = default)
{
return _context.SaveChangesAsync(cancellationToken);
}
}

View File

@@ -0,0 +1,71 @@
using System.Linq;
using Microsoft.EntityFrameworkCore;
using TakeoutSaaS.Domain.Payments.Entities;
using TakeoutSaaS.Domain.Payments.Repositories;
using TakeoutSaaS.Infrastructure.App.Persistence;
namespace TakeoutSaaS.Infrastructure.App.Repositories;
/// <summary>
/// 支付记录的 EF Core 仓储实现。
/// </summary>
public sealed class EfPaymentRepository : IPaymentRepository
{
private readonly TakeoutAppDbContext _context;
/// <summary>
/// 初始化仓储。
/// </summary>
public EfPaymentRepository(TakeoutAppDbContext context)
{
_context = context;
}
/// <inheritdoc />
public Task<PaymentRecord?> FindByIdAsync(long paymentId, long tenantId, CancellationToken cancellationToken = default)
{
return _context.PaymentRecords
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.Id == paymentId)
.FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc />
public Task<PaymentRecord?> FindByOrderIdAsync(long orderId, long tenantId, CancellationToken cancellationToken = default)
{
return _context.PaymentRecords
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.OrderId == orderId)
.FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc />
public async Task<IReadOnlyList<PaymentRefundRecord>> GetRefundsAsync(long paymentId, long tenantId, CancellationToken cancellationToken = default)
{
var refunds = await _context.PaymentRefundRecords
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.PaymentRecordId == paymentId)
.OrderByDescending(x => x.CreatedAt)
.ToListAsync(cancellationToken);
return refunds;
}
/// <inheritdoc />
public Task AddPaymentAsync(PaymentRecord payment, CancellationToken cancellationToken = default)
{
return _context.PaymentRecords.AddAsync(payment, cancellationToken).AsTask();
}
/// <inheritdoc />
public Task AddRefundAsync(PaymentRefundRecord refund, CancellationToken cancellationToken = default)
{
return _context.PaymentRefundRecords.AddAsync(refund, cancellationToken).AsTask();
}
/// <inheritdoc />
public Task SaveChangesAsync(CancellationToken cancellationToken = default)
{
return _context.SaveChangesAsync(cancellationToken);
}
}

View File

@@ -0,0 +1,227 @@
using System.Linq;
using Microsoft.EntityFrameworkCore;
using TakeoutSaaS.Domain.Products.Entities;
using TakeoutSaaS.Domain.Products.Enums;
using TakeoutSaaS.Domain.Products.Repositories;
using TakeoutSaaS.Infrastructure.App.Persistence;
namespace TakeoutSaaS.Infrastructure.App.Repositories;
/// <summary>
/// 商品聚合的 EF Core 仓储实现。
/// </summary>
public sealed class EfProductRepository : IProductRepository
{
private readonly TakeoutAppDbContext _context;
/// <summary>
/// 初始化仓储。
/// </summary>
public EfProductRepository(TakeoutAppDbContext context)
{
_context = context;
}
/// <inheritdoc />
public Task<Product?> FindByIdAsync(long productId, long tenantId, CancellationToken cancellationToken = default)
{
return _context.Products
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.Id == productId)
.FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc />
public async Task<IReadOnlyList<Product>> SearchAsync(long tenantId, long? categoryId, ProductStatus? status, CancellationToken cancellationToken = default)
{
var query = _context.Products
.AsNoTracking()
.Where(x => x.TenantId == tenantId);
if (categoryId.HasValue)
{
query = query.Where(x => x.CategoryId == categoryId.Value);
}
if (status.HasValue)
{
query = query.Where(x => x.Status == status.Value);
}
var products = await query
.OrderBy(x => x.Name)
.ToListAsync(cancellationToken);
return products;
}
/// <inheritdoc />
public async Task<IReadOnlyList<ProductCategory>> GetCategoriesAsync(long tenantId, CancellationToken cancellationToken = default)
{
var categories = await _context.ProductCategories
.AsNoTracking()
.Where(x => x.TenantId == tenantId)
.OrderBy(x => x.SortOrder)
.ToListAsync(cancellationToken);
return categories;
}
/// <inheritdoc />
public async Task<IReadOnlyList<ProductSku>> GetSkusAsync(long productId, long tenantId, CancellationToken cancellationToken = default)
{
var skus = await _context.ProductSkus
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.ProductId == productId)
.OrderBy(x => x.SortOrder)
.ToListAsync(cancellationToken);
return skus;
}
/// <inheritdoc />
public async Task<IReadOnlyList<ProductAddonGroup>> GetAddonGroupsAsync(long productId, long tenantId, CancellationToken cancellationToken = default)
{
var groups = await _context.ProductAddonGroups
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.ProductId == productId)
.OrderBy(x => x.SortOrder)
.ToListAsync(cancellationToken);
return groups;
}
/// <inheritdoc />
public async Task<IReadOnlyList<ProductAddonOption>> GetAddonOptionsAsync(long productId, long tenantId, CancellationToken cancellationToken = default)
{
var groupIds = await _context.ProductAddonGroups
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.ProductId == productId)
.Select(x => x.Id)
.ToListAsync(cancellationToken);
if (groupIds.Count == 0)
{
return Array.Empty<ProductAddonOption>();
}
var options = await _context.ProductAddonOptions
.AsNoTracking()
.Where(x => x.TenantId == tenantId && groupIds.Contains(x.AddonGroupId))
.OrderBy(x => x.SortOrder)
.ToListAsync(cancellationToken);
return options;
}
/// <inheritdoc />
public async Task<IReadOnlyList<ProductAttributeGroup>> GetAttributeGroupsAsync(long productId, long tenantId, CancellationToken cancellationToken = default)
{
var groups = await _context.ProductAttributeGroups
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.ProductId == productId)
.OrderBy(x => x.SortOrder)
.ToListAsync(cancellationToken);
return groups;
}
/// <inheritdoc />
public async Task<IReadOnlyList<ProductAttributeOption>> GetAttributeOptionsAsync(long productId, long tenantId, CancellationToken cancellationToken = default)
{
var groupIds = await _context.ProductAttributeGroups
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.ProductId == productId)
.Select(x => x.Id)
.ToListAsync(cancellationToken);
if (groupIds.Count == 0)
{
return Array.Empty<ProductAttributeOption>();
}
var options = await _context.ProductAttributeOptions
.AsNoTracking()
.Where(x => x.TenantId == tenantId && groupIds.Contains(x.AttributeGroupId))
.OrderBy(x => x.SortOrder)
.ToListAsync(cancellationToken);
return options;
}
/// <inheritdoc />
public async Task<IReadOnlyList<ProductMediaAsset>> GetMediaAssetsAsync(long productId, long tenantId, CancellationToken cancellationToken = default)
{
var assets = await _context.ProductMediaAssets
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.ProductId == productId)
.OrderBy(x => x.SortOrder)
.ToListAsync(cancellationToken);
return assets;
}
/// <inheritdoc />
public async Task<IReadOnlyList<ProductPricingRule>> GetPricingRulesAsync(long productId, long tenantId, CancellationToken cancellationToken = default)
{
var rules = await _context.ProductPricingRules
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.ProductId == productId)
.OrderBy(x => x.SortOrder)
.ToListAsync(cancellationToken);
return rules;
}
/// <inheritdoc />
public Task AddCategoryAsync(ProductCategory category, CancellationToken cancellationToken = default)
{
return _context.ProductCategories.AddAsync(category, cancellationToken).AsTask();
}
/// <inheritdoc />
public Task AddProductAsync(Product product, CancellationToken cancellationToken = default)
{
return _context.Products.AddAsync(product, cancellationToken).AsTask();
}
/// <inheritdoc />
public Task AddSkusAsync(IEnumerable<ProductSku> skus, CancellationToken cancellationToken = default)
{
return _context.ProductSkus.AddRangeAsync(skus, cancellationToken);
}
/// <inheritdoc />
public Task AddAddonGroupsAsync(IEnumerable<ProductAddonGroup> groups, IEnumerable<ProductAddonOption> options, CancellationToken cancellationToken = default)
{
var addGroupsTask = _context.ProductAddonGroups.AddRangeAsync(groups, cancellationToken);
var addOptionsTask = _context.ProductAddonOptions.AddRangeAsync(options, cancellationToken);
return Task.WhenAll(addGroupsTask, addOptionsTask);
}
/// <inheritdoc />
public Task AddAttributeGroupsAsync(IEnumerable<ProductAttributeGroup> groups, IEnumerable<ProductAttributeOption> options, CancellationToken cancellationToken = default)
{
var addGroupsTask = _context.ProductAttributeGroups.AddRangeAsync(groups, cancellationToken);
var addOptionsTask = _context.ProductAttributeOptions.AddRangeAsync(options, cancellationToken);
return Task.WhenAll(addGroupsTask, addOptionsTask);
}
/// <inheritdoc />
public Task AddMediaAssetsAsync(IEnumerable<ProductMediaAsset> assets, CancellationToken cancellationToken = default)
{
return _context.ProductMediaAssets.AddRangeAsync(assets, cancellationToken);
}
/// <inheritdoc />
public Task AddPricingRulesAsync(IEnumerable<ProductPricingRule> rules, CancellationToken cancellationToken = default)
{
return _context.ProductPricingRules.AddRangeAsync(rules, cancellationToken);
}
/// <inheritdoc />
public Task SaveChangesAsync(CancellationToken cancellationToken = default)
{
return _context.SaveChangesAsync(cancellationToken);
}
}

View File

@@ -0,0 +1,174 @@
using System.Linq;
using Microsoft.EntityFrameworkCore;
using TakeoutSaaS.Domain.Stores.Entities;
using TakeoutSaaS.Domain.Stores.Enums;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Infrastructure.App.Persistence;
namespace TakeoutSaaS.Infrastructure.App.Repositories;
/// <summary>
/// 门店聚合的 EF Core 仓储实现。
/// </summary>
public sealed class EfStoreRepository : IStoreRepository
{
private readonly TakeoutAppDbContext _context;
/// <summary>
/// 初始化仓储。
/// </summary>
public EfStoreRepository(TakeoutAppDbContext context)
{
_context = context;
}
/// <inheritdoc />
public Task<Store?> FindByIdAsync(long storeId, long tenantId, CancellationToken cancellationToken = default)
{
return _context.Stores
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.Id == storeId)
.FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc />
public async Task<IReadOnlyList<Store>> SearchAsync(long tenantId, StoreStatus? status, CancellationToken cancellationToken = default)
{
var query = _context.Stores
.AsNoTracking()
.Where(x => x.TenantId == tenantId);
if (status.HasValue)
{
query = query.Where(x => x.Status == status.Value);
}
var stores = await query
.OrderBy(x => x.Name)
.ToListAsync(cancellationToken);
return stores;
}
/// <inheritdoc />
public async Task<IReadOnlyList<StoreBusinessHour>> GetBusinessHoursAsync(long storeId, long tenantId, CancellationToken cancellationToken = default)
{
var hours = await _context.StoreBusinessHours
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.StoreId == storeId)
.OrderBy(x => x.DayOfWeek)
.ThenBy(x => x.StartTime)
.ToListAsync(cancellationToken);
return hours;
}
/// <inheritdoc />
public async Task<IReadOnlyList<StoreDeliveryZone>> GetDeliveryZonesAsync(long storeId, long tenantId, CancellationToken cancellationToken = default)
{
var zones = await _context.StoreDeliveryZones
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.StoreId == storeId)
.OrderBy(x => x.SortOrder)
.ToListAsync(cancellationToken);
return zones;
}
/// <inheritdoc />
public async Task<IReadOnlyList<StoreHoliday>> GetHolidaysAsync(long storeId, long tenantId, CancellationToken cancellationToken = default)
{
var holidays = await _context.StoreHolidays
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.StoreId == storeId)
.OrderBy(x => x.Date)
.ToListAsync(cancellationToken);
return holidays;
}
/// <inheritdoc />
public async Task<IReadOnlyList<StoreTableArea>> GetTableAreasAsync(long storeId, long tenantId, CancellationToken cancellationToken = default)
{
var areas = await _context.StoreTableAreas
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.StoreId == storeId)
.OrderBy(x => x.SortOrder)
.ToListAsync(cancellationToken);
return areas;
}
/// <inheritdoc />
public async Task<IReadOnlyList<StoreTable>> GetTablesAsync(long storeId, long tenantId, CancellationToken cancellationToken = default)
{
var tables = await _context.StoreTables
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.StoreId == storeId)
.OrderBy(x => x.TableCode)
.ToListAsync(cancellationToken);
return tables;
}
/// <inheritdoc />
public async Task<IReadOnlyList<StoreEmployeeShift>> GetShiftsAsync(long storeId, long tenantId, CancellationToken cancellationToken = default)
{
var shifts = await _context.StoreEmployeeShifts
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.StoreId == storeId)
.OrderBy(x => x.ShiftDate)
.ThenBy(x => x.StartTime)
.ToListAsync(cancellationToken);
return shifts;
}
/// <inheritdoc />
public Task AddStoreAsync(Store store, CancellationToken cancellationToken = default)
{
return _context.Stores.AddAsync(store, cancellationToken).AsTask();
}
/// <inheritdoc />
public Task AddBusinessHoursAsync(IEnumerable<StoreBusinessHour> hours, CancellationToken cancellationToken = default)
{
return _context.StoreBusinessHours.AddRangeAsync(hours, cancellationToken);
}
/// <inheritdoc />
public Task AddDeliveryZonesAsync(IEnumerable<StoreDeliveryZone> zones, CancellationToken cancellationToken = default)
{
return _context.StoreDeliveryZones.AddRangeAsync(zones, cancellationToken);
}
/// <inheritdoc />
public Task AddHolidaysAsync(IEnumerable<StoreHoliday> holidays, CancellationToken cancellationToken = default)
{
return _context.StoreHolidays.AddRangeAsync(holidays, cancellationToken);
}
/// <inheritdoc />
public Task AddTableAreasAsync(IEnumerable<StoreTableArea> areas, CancellationToken cancellationToken = default)
{
return _context.StoreTableAreas.AddRangeAsync(areas, cancellationToken);
}
/// <inheritdoc />
public Task AddTablesAsync(IEnumerable<StoreTable> tables, CancellationToken cancellationToken = default)
{
return _context.StoreTables.AddRangeAsync(tables, cancellationToken);
}
/// <inheritdoc />
public Task AddShiftsAsync(IEnumerable<StoreEmployeeShift> shifts, CancellationToken cancellationToken = default)
{
return _context.StoreEmployeeShifts.AddRangeAsync(shifts, cancellationToken);
}
/// <inheritdoc />
public Task SaveChangesAsync(CancellationToken cancellationToken = default)
{
return _context.SaveChangesAsync(cancellationToken);
}
}

View File

@@ -3,7 +3,9 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using TakeoutSaaS.Infrastructure.Common.Options;
using TakeoutSaaS.Infrastructure.Common.Persistence;
using TakeoutSaaS.Shared.Abstractions.Ids;
using TakeoutSaaS.Shared.Abstractions.Data;
using TakeoutSaaS.Shared.Kernel.Ids;
namespace TakeoutSaaS.Infrastructure.Common.Extensions;
@@ -25,6 +27,17 @@ public static class DatabaseServiceCollectionExtensions
.ValidateDataAnnotations()
.ValidateOnStart();
services.AddOptions<IdGeneratorOptions>()
.Bind(configuration.GetSection(IdGeneratorOptions.SectionName))
.ValidateDataAnnotations()
.ValidateOnStart();
services.AddSingleton<IIdGenerator>(sp =>
{
var options = sp.GetRequiredService<Microsoft.Extensions.Options.IOptions<IdGeneratorOptions>>().Value;
return new SnowflakeIdGenerator(options.WorkerId, options.DatacenterId);
});
services.AddSingleton<IDatabaseConnectionFactory, DatabaseConnectionFactory>();
services.AddScoped<IDapperExecutor, DapperExecutor>();
return services;

View File

@@ -3,15 +3,20 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using TakeoutSaaS.Shared.Abstractions.Entities;
using TakeoutSaaS.Shared.Abstractions.Security;
using TakeoutSaaS.Shared.Abstractions.Ids;
namespace TakeoutSaaS.Infrastructure.Common.Persistence;
/// <summary>
/// 应用基础 DbContext统一处理审计字段、软删除与全局查询过滤。
/// </summary>
public abstract class AppDbContext(DbContextOptions options, ICurrentUserAccessor? currentUserAccessor = null) : DbContext(options)
public abstract class AppDbContext(
DbContextOptions options,
ICurrentUserAccessor? currentUserAccessor = null,
IIdGenerator? idGenerator = null) : DbContext(options)
{
private readonly ICurrentUserAccessor? _currentUserAccessor = currentUserAccessor;
private readonly IIdGenerator? _idGenerator = idGenerator;
/// <summary>
/// 构建模型时应用软删除过滤器。
@@ -50,10 +55,35 @@ public abstract class AppDbContext(DbContextOptions options, ICurrentUserAccesso
/// </summary>
protected virtual void OnBeforeSaving()
{
ApplyIdGeneration();
ApplySoftDeleteMetadata();
ApplyAuditMetadata();
}
/// <summary>
/// 为新增实体生成雪花 ID。
/// </summary>
private void ApplyIdGeneration()
{
if (_idGenerator == null)
{
return;
}
foreach (var entry in ChangeTracker.Entries<EntityBase>())
{
if (entry.State != EntityState.Added)
{
continue;
}
if (entry.Entity.Id == 0)
{
entry.Entity.Id = _idGenerator.NextId();
}
}
}
/// <summary>
/// 将软删除实体的删除操作转换为设置 DeletedAt。
/// </summary>
@@ -114,10 +144,10 @@ public abstract class AppDbContext(DbContextOptions options, ICurrentUserAccesso
}
}
private Guid? GetCurrentUserIdOrNull()
private long? GetCurrentUserIdOrNull()
{
var userId = _currentUserAccessor?.UserId ?? Guid.Empty;
return userId == Guid.Empty ? null : userId;
var userId = _currentUserAccessor?.UserId ?? 0;
return userId == 0 ? null : userId;
}
/// <summary>

View File

@@ -141,12 +141,12 @@ internal abstract class DesignTimeDbContextFactoryBase<TContext> : IDesignTimeDb
private sealed class DesignTimeTenantProvider : ITenantProvider
{
public Guid GetCurrentTenantId() => Guid.Empty;
public long GetCurrentTenantId() => 0;
}
private sealed class DesignTimeCurrentUserAccessor : ICurrentUserAccessor
{
public Guid UserId => Guid.Empty;
public long UserId => 0;
public bool IsAuthenticated => false;
}
}

View File

@@ -3,6 +3,7 @@ using Microsoft.EntityFrameworkCore;
using TakeoutSaaS.Shared.Abstractions.Entities;
using TakeoutSaaS.Shared.Abstractions.Security;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
using TakeoutSaaS.Shared.Abstractions.Ids;
namespace TakeoutSaaS.Infrastructure.Common.Persistence;
@@ -12,14 +13,15 @@ namespace TakeoutSaaS.Infrastructure.Common.Persistence;
public abstract class TenantAwareDbContext(
DbContextOptions options,
ITenantProvider tenantProvider,
ICurrentUserAccessor? currentUserAccessor = null) : AppDbContext(options, currentUserAccessor)
ICurrentUserAccessor? currentUserAccessor = null,
IIdGenerator? idGenerator = null) : AppDbContext(options, currentUserAccessor, idGenerator)
{
private readonly ITenantProvider _tenantProvider = tenantProvider ?? throw new ArgumentNullException(nameof(tenantProvider));
/// <summary>
/// 当前请求租户 ID。
/// </summary>
protected Guid CurrentTenantId => _tenantProvider.GetCurrentTenantId();
protected long CurrentTenantId => _tenantProvider.GetCurrentTenantId();
/// <summary>
/// 保存前填充租户元数据并执行基础处理。
@@ -71,7 +73,7 @@ public abstract class TenantAwareDbContext(
foreach (var entry in ChangeTracker.Entries<IMultiTenantEntity>())
{
if (entry.State == EntityState.Added && entry.Entity.TenantId == Guid.Empty && tenantId != Guid.Empty)
if (entry.State == EntityState.Added && entry.Entity.TenantId == 0 && tenantId != 0)
{
entry.Entity.TenantId = tenantId;
}

View File

@@ -1,172 +0,0 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using TakeoutSaaS.Infrastructure.Dictionary.Persistence;
#nullable disable
namespace TakeoutSaaS.Infrastructure.Dictionary.Migrations
{
[DbContext(typeof(DictionaryDbContext))]
[Migration("20251201042346_InitialDictionary")]
partial class InitialDictionary
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("TakeoutSaaS.Domain.Dictionary.Entities.DictionaryGroup", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uuid");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uuid");
b.Property<string>("Description")
.HasMaxLength(512)
.HasColumnType("character varying(512)");
b.Property<bool>("IsEnabled")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
.HasDefaultValue(true);
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<int>("Scope")
.HasColumnType("integer");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("TenantId");
b.HasIndex("TenantId", "Code")
.IsUnique();
b.ToTable("dictionary_groups", (string)null);
});
modelBuilder.Entity("TakeoutSaaS.Domain.Dictionary.Entities.DictionaryItem", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uuid");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uuid");
b.Property<string>("Description")
.HasMaxLength(512)
.HasColumnType("character varying(512)");
b.Property<Guid>("GroupId")
.HasColumnType("uuid");
b.Property<bool>("IsDefault")
.HasColumnType("boolean");
b.Property<bool>("IsEnabled")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
.HasDefaultValue(true);
b.Property<string>("Key")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<int>("SortOrder")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasDefaultValue(100);
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uuid");
b.Property<string>("Value")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.HasKey("Id");
b.HasIndex("TenantId");
b.HasIndex("GroupId", "Key")
.IsUnique();
b.ToTable("dictionary_items", (string)null);
});
modelBuilder.Entity("TakeoutSaaS.Domain.Dictionary.Entities.DictionaryItem", b =>
{
b.HasOne("TakeoutSaaS.Domain.Dictionary.Entities.DictionaryGroup", "Group")
.WithMany("Items")
.HasForeignKey("GroupId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Group");
});
modelBuilder.Entity("TakeoutSaaS.Domain.Dictionary.Entities.DictionaryGroup", b =>
{
b.Navigation("Items");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -1,101 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace TakeoutSaaS.Infrastructure.Dictionary.Migrations
{
/// <inheritdoc />
public partial class InitialDictionary : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "dictionary_groups",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
Code = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
Name = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
Scope = table.Column<int>(type: "integer", nullable: false),
Description = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: true),
IsEnabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CreatedBy = table.Column<Guid>(type: "uuid", nullable: true),
UpdatedBy = table.Column<Guid>(type: "uuid", nullable: true),
DeletedBy = table.Column<Guid>(type: "uuid", nullable: true),
TenantId = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_dictionary_groups", x => x.Id);
});
migrationBuilder.CreateTable(
name: "dictionary_items",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
GroupId = table.Column<Guid>(type: "uuid", nullable: false),
Key = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
Value = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
IsDefault = table.Column<bool>(type: "boolean", nullable: false),
IsEnabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true),
SortOrder = table.Column<int>(type: "integer", nullable: false, defaultValue: 100),
Description = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: true),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CreatedBy = table.Column<Guid>(type: "uuid", nullable: true),
UpdatedBy = table.Column<Guid>(type: "uuid", nullable: true),
DeletedBy = table.Column<Guid>(type: "uuid", nullable: true),
TenantId = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_dictionary_items", x => x.Id);
table.ForeignKey(
name: "FK_dictionary_items_dictionary_groups_GroupId",
column: x => x.GroupId,
principalTable: "dictionary_groups",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_dictionary_groups_TenantId",
table: "dictionary_groups",
column: "TenantId");
migrationBuilder.CreateIndex(
name: "IX_dictionary_groups_TenantId_Code",
table: "dictionary_groups",
columns: new[] { "TenantId", "Code" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_dictionary_items_GroupId_Key",
table: "dictionary_items",
columns: new[] { "GroupId", "Key" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_dictionary_items_TenantId",
table: "dictionary_items",
column: "TenantId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "dictionary_items");
migrationBuilder.DropTable(
name: "dictionary_groups");
}
}
}

View File

@@ -1,599 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace TakeoutSaaS.Infrastructure.Dictionary.Migrations
{
/// <inheritdoc />
public partial class AddEntityComments : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterTable(
name: "dictionary_items",
comment: "参数字典项。");
migrationBuilder.AlterTable(
name: "dictionary_groups",
comment: "参数字典分组(系统参数、业务参数)。");
migrationBuilder.AlterColumn<string>(
name: "Value",
table: "dictionary_items",
type: "character varying(256)",
maxLength: 256,
nullable: false,
comment: "字典项值。",
oldClrType: typeof(string),
oldType: "character varying(256)",
oldMaxLength: 256);
migrationBuilder.AlterColumn<Guid>(
name: "UpdatedBy",
table: "dictionary_items",
type: "uuid",
nullable: true,
comment: "最后更新人用户标识,匿名或系统操作时为 null。",
oldClrType: typeof(Guid),
oldType: "uuid",
oldNullable: true);
migrationBuilder.AlterColumn<DateTime>(
name: "UpdatedAt",
table: "dictionary_items",
type: "timestamp with time zone",
nullable: true,
comment: "最近一次更新时间UTC从未更新时为 null。",
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone",
oldNullable: true);
migrationBuilder.AlterColumn<Guid>(
name: "TenantId",
table: "dictionary_items",
type: "uuid",
nullable: false,
comment: "所属租户 ID。",
oldClrType: typeof(Guid),
oldType: "uuid");
migrationBuilder.AlterColumn<int>(
name: "SortOrder",
table: "dictionary_items",
type: "integer",
nullable: false,
defaultValue: 100,
comment: "排序值,越小越靠前。",
oldClrType: typeof(int),
oldType: "integer",
oldDefaultValue: 100);
migrationBuilder.AlterColumn<string>(
name: "Key",
table: "dictionary_items",
type: "character varying(64)",
maxLength: 64,
nullable: false,
comment: "字典项键。",
oldClrType: typeof(string),
oldType: "character varying(64)",
oldMaxLength: 64);
migrationBuilder.AlterColumn<bool>(
name: "IsEnabled",
table: "dictionary_items",
type: "boolean",
nullable: false,
defaultValue: true,
comment: "是否启用。",
oldClrType: typeof(bool),
oldType: "boolean",
oldDefaultValue: true);
migrationBuilder.AlterColumn<bool>(
name: "IsDefault",
table: "dictionary_items",
type: "boolean",
nullable: false,
comment: "是否默认项。",
oldClrType: typeof(bool),
oldType: "boolean");
migrationBuilder.AlterColumn<Guid>(
name: "GroupId",
table: "dictionary_items",
type: "uuid",
nullable: false,
comment: "关联分组 ID。",
oldClrType: typeof(Guid),
oldType: "uuid");
migrationBuilder.AlterColumn<string>(
name: "Description",
table: "dictionary_items",
type: "character varying(512)",
maxLength: 512,
nullable: true,
comment: "描述信息。",
oldClrType: typeof(string),
oldType: "character varying(512)",
oldMaxLength: 512,
oldNullable: true);
migrationBuilder.AlterColumn<Guid>(
name: "DeletedBy",
table: "dictionary_items",
type: "uuid",
nullable: true,
comment: "删除人用户标识(软删除),未删除时为 null。",
oldClrType: typeof(Guid),
oldType: "uuid",
oldNullable: true);
migrationBuilder.AlterColumn<DateTime>(
name: "DeletedAt",
table: "dictionary_items",
type: "timestamp with time zone",
nullable: true,
comment: "软删除时间UTC未删除时为 null。",
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone",
oldNullable: true);
migrationBuilder.AlterColumn<Guid>(
name: "CreatedBy",
table: "dictionary_items",
type: "uuid",
nullable: true,
comment: "创建人用户标识,匿名或系统操作时为 null。",
oldClrType: typeof(Guid),
oldType: "uuid",
oldNullable: true);
migrationBuilder.AlterColumn<DateTime>(
name: "CreatedAt",
table: "dictionary_items",
type: "timestamp with time zone",
nullable: false,
comment: "创建时间UTC。",
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone");
migrationBuilder.AlterColumn<Guid>(
name: "Id",
table: "dictionary_items",
type: "uuid",
nullable: false,
comment: "实体唯一标识。",
oldClrType: typeof(Guid),
oldType: "uuid");
migrationBuilder.AlterColumn<Guid>(
name: "UpdatedBy",
table: "dictionary_groups",
type: "uuid",
nullable: true,
comment: "最后更新人用户标识,匿名或系统操作时为 null。",
oldClrType: typeof(Guid),
oldType: "uuid",
oldNullable: true);
migrationBuilder.AlterColumn<DateTime>(
name: "UpdatedAt",
table: "dictionary_groups",
type: "timestamp with time zone",
nullable: true,
comment: "最近一次更新时间UTC从未更新时为 null。",
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone",
oldNullable: true);
migrationBuilder.AlterColumn<Guid>(
name: "TenantId",
table: "dictionary_groups",
type: "uuid",
nullable: false,
comment: "所属租户 ID。",
oldClrType: typeof(Guid),
oldType: "uuid");
migrationBuilder.AlterColumn<int>(
name: "Scope",
table: "dictionary_groups",
type: "integer",
nullable: false,
comment: "分组作用域:系统/业务。",
oldClrType: typeof(int),
oldType: "integer");
migrationBuilder.AlterColumn<string>(
name: "Name",
table: "dictionary_groups",
type: "character varying(128)",
maxLength: 128,
nullable: false,
comment: "分组名称。",
oldClrType: typeof(string),
oldType: "character varying(128)",
oldMaxLength: 128);
migrationBuilder.AlterColumn<bool>(
name: "IsEnabled",
table: "dictionary_groups",
type: "boolean",
nullable: false,
defaultValue: true,
comment: "是否启用。",
oldClrType: typeof(bool),
oldType: "boolean",
oldDefaultValue: true);
migrationBuilder.AlterColumn<string>(
name: "Description",
table: "dictionary_groups",
type: "character varying(512)",
maxLength: 512,
nullable: true,
comment: "描述信息。",
oldClrType: typeof(string),
oldType: "character varying(512)",
oldMaxLength: 512,
oldNullable: true);
migrationBuilder.AlterColumn<Guid>(
name: "DeletedBy",
table: "dictionary_groups",
type: "uuid",
nullable: true,
comment: "删除人用户标识(软删除),未删除时为 null。",
oldClrType: typeof(Guid),
oldType: "uuid",
oldNullable: true);
migrationBuilder.AlterColumn<DateTime>(
name: "DeletedAt",
table: "dictionary_groups",
type: "timestamp with time zone",
nullable: true,
comment: "软删除时间UTC未删除时为 null。",
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone",
oldNullable: true);
migrationBuilder.AlterColumn<Guid>(
name: "CreatedBy",
table: "dictionary_groups",
type: "uuid",
nullable: true,
comment: "创建人用户标识,匿名或系统操作时为 null。",
oldClrType: typeof(Guid),
oldType: "uuid",
oldNullable: true);
migrationBuilder.AlterColumn<DateTime>(
name: "CreatedAt",
table: "dictionary_groups",
type: "timestamp with time zone",
nullable: false,
comment: "创建时间UTC。",
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone");
migrationBuilder.AlterColumn<string>(
name: "Code",
table: "dictionary_groups",
type: "character varying(64)",
maxLength: 64,
nullable: false,
comment: "分组编码(唯一)。",
oldClrType: typeof(string),
oldType: "character varying(64)",
oldMaxLength: 64);
migrationBuilder.AlterColumn<Guid>(
name: "Id",
table: "dictionary_groups",
type: "uuid",
nullable: false,
comment: "实体唯一标识。",
oldClrType: typeof(Guid),
oldType: "uuid");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterTable(
name: "dictionary_items",
oldComment: "参数字典项。");
migrationBuilder.AlterTable(
name: "dictionary_groups",
oldComment: "参数字典分组(系统参数、业务参数)。");
migrationBuilder.AlterColumn<string>(
name: "Value",
table: "dictionary_items",
type: "character varying(256)",
maxLength: 256,
nullable: false,
oldClrType: typeof(string),
oldType: "character varying(256)",
oldMaxLength: 256,
oldComment: "字典项值。");
migrationBuilder.AlterColumn<Guid>(
name: "UpdatedBy",
table: "dictionary_items",
type: "uuid",
nullable: true,
oldClrType: typeof(Guid),
oldType: "uuid",
oldNullable: true,
oldComment: "最后更新人用户标识,匿名或系统操作时为 null。");
migrationBuilder.AlterColumn<DateTime>(
name: "UpdatedAt",
table: "dictionary_items",
type: "timestamp with time zone",
nullable: true,
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone",
oldNullable: true,
oldComment: "最近一次更新时间UTC从未更新时为 null。");
migrationBuilder.AlterColumn<Guid>(
name: "TenantId",
table: "dictionary_items",
type: "uuid",
nullable: false,
oldClrType: typeof(Guid),
oldType: "uuid",
oldComment: "所属租户 ID。");
migrationBuilder.AlterColumn<int>(
name: "SortOrder",
table: "dictionary_items",
type: "integer",
nullable: false,
defaultValue: 100,
oldClrType: typeof(int),
oldType: "integer",
oldDefaultValue: 100,
oldComment: "排序值,越小越靠前。");
migrationBuilder.AlterColumn<string>(
name: "Key",
table: "dictionary_items",
type: "character varying(64)",
maxLength: 64,
nullable: false,
oldClrType: typeof(string),
oldType: "character varying(64)",
oldMaxLength: 64,
oldComment: "字典项键。");
migrationBuilder.AlterColumn<bool>(
name: "IsEnabled",
table: "dictionary_items",
type: "boolean",
nullable: false,
defaultValue: true,
oldClrType: typeof(bool),
oldType: "boolean",
oldDefaultValue: true,
oldComment: "是否启用。");
migrationBuilder.AlterColumn<bool>(
name: "IsDefault",
table: "dictionary_items",
type: "boolean",
nullable: false,
oldClrType: typeof(bool),
oldType: "boolean",
oldComment: "是否默认项。");
migrationBuilder.AlterColumn<Guid>(
name: "GroupId",
table: "dictionary_items",
type: "uuid",
nullable: false,
oldClrType: typeof(Guid),
oldType: "uuid",
oldComment: "关联分组 ID。");
migrationBuilder.AlterColumn<string>(
name: "Description",
table: "dictionary_items",
type: "character varying(512)",
maxLength: 512,
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(512)",
oldMaxLength: 512,
oldNullable: true,
oldComment: "描述信息。");
migrationBuilder.AlterColumn<Guid>(
name: "DeletedBy",
table: "dictionary_items",
type: "uuid",
nullable: true,
oldClrType: typeof(Guid),
oldType: "uuid",
oldNullable: true,
oldComment: "删除人用户标识(软删除),未删除时为 null。");
migrationBuilder.AlterColumn<DateTime>(
name: "DeletedAt",
table: "dictionary_items",
type: "timestamp with time zone",
nullable: true,
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone",
oldNullable: true,
oldComment: "软删除时间UTC未删除时为 null。");
migrationBuilder.AlterColumn<Guid>(
name: "CreatedBy",
table: "dictionary_items",
type: "uuid",
nullable: true,
oldClrType: typeof(Guid),
oldType: "uuid",
oldNullable: true,
oldComment: "创建人用户标识,匿名或系统操作时为 null。");
migrationBuilder.AlterColumn<DateTime>(
name: "CreatedAt",
table: "dictionary_items",
type: "timestamp with time zone",
nullable: false,
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone",
oldComment: "创建时间UTC。");
migrationBuilder.AlterColumn<Guid>(
name: "Id",
table: "dictionary_items",
type: "uuid",
nullable: false,
oldClrType: typeof(Guid),
oldType: "uuid",
oldComment: "实体唯一标识。");
migrationBuilder.AlterColumn<Guid>(
name: "UpdatedBy",
table: "dictionary_groups",
type: "uuid",
nullable: true,
oldClrType: typeof(Guid),
oldType: "uuid",
oldNullable: true,
oldComment: "最后更新人用户标识,匿名或系统操作时为 null。");
migrationBuilder.AlterColumn<DateTime>(
name: "UpdatedAt",
table: "dictionary_groups",
type: "timestamp with time zone",
nullable: true,
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone",
oldNullable: true,
oldComment: "最近一次更新时间UTC从未更新时为 null。");
migrationBuilder.AlterColumn<Guid>(
name: "TenantId",
table: "dictionary_groups",
type: "uuid",
nullable: false,
oldClrType: typeof(Guid),
oldType: "uuid",
oldComment: "所属租户 ID。");
migrationBuilder.AlterColumn<int>(
name: "Scope",
table: "dictionary_groups",
type: "integer",
nullable: false,
oldClrType: typeof(int),
oldType: "integer",
oldComment: "分组作用域:系统/业务。");
migrationBuilder.AlterColumn<string>(
name: "Name",
table: "dictionary_groups",
type: "character varying(128)",
maxLength: 128,
nullable: false,
oldClrType: typeof(string),
oldType: "character varying(128)",
oldMaxLength: 128,
oldComment: "分组名称。");
migrationBuilder.AlterColumn<bool>(
name: "IsEnabled",
table: "dictionary_groups",
type: "boolean",
nullable: false,
defaultValue: true,
oldClrType: typeof(bool),
oldType: "boolean",
oldDefaultValue: true,
oldComment: "是否启用。");
migrationBuilder.AlterColumn<string>(
name: "Description",
table: "dictionary_groups",
type: "character varying(512)",
maxLength: 512,
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(512)",
oldMaxLength: 512,
oldNullable: true,
oldComment: "描述信息。");
migrationBuilder.AlterColumn<Guid>(
name: "DeletedBy",
table: "dictionary_groups",
type: "uuid",
nullable: true,
oldClrType: typeof(Guid),
oldType: "uuid",
oldNullable: true,
oldComment: "删除人用户标识(软删除),未删除时为 null。");
migrationBuilder.AlterColumn<DateTime>(
name: "DeletedAt",
table: "dictionary_groups",
type: "timestamp with time zone",
nullable: true,
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone",
oldNullable: true,
oldComment: "软删除时间UTC未删除时为 null。");
migrationBuilder.AlterColumn<Guid>(
name: "CreatedBy",
table: "dictionary_groups",
type: "uuid",
nullable: true,
oldClrType: typeof(Guid),
oldType: "uuid",
oldNullable: true,
oldComment: "创建人用户标识,匿名或系统操作时为 null。");
migrationBuilder.AlterColumn<DateTime>(
name: "CreatedAt",
table: "dictionary_groups",
type: "timestamp with time zone",
nullable: false,
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone",
oldComment: "创建时间UTC。");
migrationBuilder.AlterColumn<string>(
name: "Code",
table: "dictionary_groups",
type: "character varying(64)",
maxLength: 64,
nullable: false,
oldClrType: typeof(string),
oldType: "character varying(64)",
oldMaxLength: 64,
oldComment: "分组编码(唯一)。");
migrationBuilder.AlterColumn<Guid>(
name: "Id",
table: "dictionary_groups",
type: "uuid",
nullable: false,
oldClrType: typeof(Guid),
oldType: "uuid",
oldComment: "实体唯一标识。");
}
}
}

View File

@@ -2,6 +2,7 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using TakeoutSaaS.Domain.Dictionary.Entities;
using TakeoutSaaS.Infrastructure.Common.Persistence;
using TakeoutSaaS.Shared.Abstractions.Ids;
using TakeoutSaaS.Shared.Abstractions.Security;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
@@ -13,8 +14,9 @@ namespace TakeoutSaaS.Infrastructure.Dictionary.Persistence;
public sealed class DictionaryDbContext(
DbContextOptions<DictionaryDbContext> options,
ITenantProvider tenantProvider,
ICurrentUserAccessor? currentUserAccessor = null)
: TenantAwareDbContext(options, tenantProvider, currentUserAccessor)
ICurrentUserAccessor? currentUserAccessor = null,
IIdGenerator? idGenerator = null)
: TenantAwareDbContext(options, tenantProvider, currentUserAccessor, idGenerator)
{
/// <summary>
/// 字典分组集。

View File

@@ -19,7 +19,7 @@ public sealed class EfDictionaryRepository : IDictionaryRepository
_context = context;
}
public Task<DictionaryGroup?> FindGroupByIdAsync(Guid id, CancellationToken cancellationToken = default)
public Task<DictionaryGroup?> FindGroupByIdAsync(long id, CancellationToken cancellationToken = default)
=> _context.DictionaryGroups.FirstOrDefaultAsync(group => group.Id == id, cancellationToken);
public Task<DictionaryGroup?> FindGroupByCodeAsync(string code, CancellationToken cancellationToken = default)
@@ -50,10 +50,10 @@ public sealed class EfDictionaryRepository : IDictionaryRepository
return Task.CompletedTask;
}
public Task<DictionaryItem?> FindItemByIdAsync(Guid id, CancellationToken cancellationToken = default)
public Task<DictionaryItem?> FindItemByIdAsync(long id, CancellationToken cancellationToken = default)
=> _context.DictionaryItems.FirstOrDefaultAsync(item => item.Id == id, cancellationToken);
public async Task<IReadOnlyList<DictionaryItem>> GetItemsByGroupIdAsync(Guid groupId, CancellationToken cancellationToken = default)
public async Task<IReadOnlyList<DictionaryItem>> GetItemsByGroupIdAsync(long groupId, CancellationToken cancellationToken = default)
{
return await _context.DictionaryItems
.AsNoTracking()
@@ -77,7 +77,7 @@ public sealed class EfDictionaryRepository : IDictionaryRepository
public Task SaveChangesAsync(CancellationToken cancellationToken = default)
=> _context.SaveChangesAsync(cancellationToken);
public async Task<IReadOnlyList<DictionaryItem>> GetItemsByCodesAsync(IEnumerable<string> codes, Guid tenantId, bool includeSystem, CancellationToken cancellationToken = default)
public async Task<IReadOnlyList<DictionaryItem>> GetItemsByCodesAsync(IEnumerable<string> codes, long tenantId, bool includeSystem, CancellationToken cancellationToken = default)
{
var normalizedCodes = codes
.Where(code => !string.IsNullOrWhiteSpace(code))
@@ -96,7 +96,7 @@ public sealed class EfDictionaryRepository : IDictionaryRepository
.Include(item => item.Group)
.Where(item => normalizedCodes.Contains(item.Group!.Code));
query = query.Where(item => item.TenantId == tenantId || (includeSystem && item.TenantId == Guid.Empty));
query = query.Where(item => item.TenantId == tenantId || (includeSystem && item.TenantId == 0));
return await query
.OrderBy(item => item.SortOrder)

View File

@@ -22,7 +22,7 @@ public sealed class DistributedDictionaryCache : IDictionaryCache
_options = options.Value;
}
public async Task<IReadOnlyList<DictionaryItemDto>?> GetAsync(Guid tenantId, string code, CancellationToken cancellationToken = default)
public async Task<IReadOnlyList<DictionaryItemDto>?> GetAsync(long tenantId, string code, CancellationToken cancellationToken = default)
{
var cacheKey = BuildKey(tenantId, code);
var payload = await _cache.GetAsync(cacheKey, cancellationToken);
@@ -34,7 +34,7 @@ public sealed class DistributedDictionaryCache : IDictionaryCache
return JsonSerializer.Deserialize<List<DictionaryItemDto>>(payload, _serializerOptions);
}
public Task SetAsync(Guid tenantId, string code, IReadOnlyList<DictionaryItemDto> items, CancellationToken cancellationToken = default)
public Task SetAsync(long tenantId, string code, IReadOnlyList<DictionaryItemDto> items, CancellationToken cancellationToken = default)
{
var cacheKey = BuildKey(tenantId, code);
var payload = JsonSerializer.SerializeToUtf8Bytes(items, _serializerOptions);
@@ -45,12 +45,12 @@ public sealed class DistributedDictionaryCache : IDictionaryCache
return _cache.SetAsync(cacheKey, payload, options, cancellationToken);
}
public Task RemoveAsync(Guid tenantId, string code, CancellationToken cancellationToken = default)
public Task RemoveAsync(long tenantId, string code, CancellationToken cancellationToken = default)
{
var cacheKey = BuildKey(tenantId, code);
return _cache.RemoveAsync(cacheKey, cancellationToken);
}
private static string BuildKey(Guid tenantId, string code)
=> $"dictionary:{tenantId.ToString().ToLowerInvariant()}:{code.ToLowerInvariant()}";
private static string BuildKey(long tenantId, string code)
=> $"dictionary:{tenantId}:{code.ToLowerInvariant()}";
}

View File

@@ -1,152 +0,0 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using TakeoutSaaS.Infrastructure.Identity.Persistence;
#nullable disable
namespace TakeoutSaaS.Infrastructure.Identity.Migrations
{
[DbContext(typeof(IdentityDbContext))]
[Migration("20251201042324_InitialIdentity")]
partial class InitialIdentity
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.IdentityUser", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Account")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("Avatar")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uuid");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uuid");
b.Property<string>("DisplayName")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<Guid?>("MerchantId")
.HasColumnType("uuid");
b.Property<string>("PasswordHash")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("Permissions")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Roles")
.IsRequired()
.HasColumnType("text");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("TenantId");
b.HasIndex("TenantId", "Account")
.IsUnique();
b.ToTable("identity_users", (string)null);
});
modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.MiniUser", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Avatar")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uuid");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uuid");
b.Property<string>("Nickname")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("OpenId")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<Guid>("TenantId")
.HasColumnType("uuid");
b.Property<string>("UnionId")
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("TenantId");
b.HasIndex("TenantId", "OpenId")
.IsUnique();
b.ToTable("mini_users", (string)null);
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -1,94 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace TakeoutSaaS.Infrastructure.Identity.Migrations
{
/// <inheritdoc />
public partial class InitialIdentity : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "identity_users",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
Account = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
DisplayName = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
PasswordHash = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
MerchantId = table.Column<Guid>(type: "uuid", nullable: true),
Roles = table.Column<string>(type: "text", nullable: false),
Permissions = table.Column<string>(type: "text", nullable: false),
Avatar = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CreatedBy = table.Column<Guid>(type: "uuid", nullable: true),
UpdatedBy = table.Column<Guid>(type: "uuid", nullable: true),
DeletedBy = table.Column<Guid>(type: "uuid", nullable: true),
TenantId = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_identity_users", x => x.Id);
});
migrationBuilder.CreateTable(
name: "mini_users",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
OpenId = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
UnionId = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true),
Nickname = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
Avatar = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
CreatedBy = table.Column<Guid>(type: "uuid", nullable: true),
UpdatedBy = table.Column<Guid>(type: "uuid", nullable: true),
DeletedBy = table.Column<Guid>(type: "uuid", nullable: true),
TenantId = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_mini_users", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_identity_users_TenantId",
table: "identity_users",
column: "TenantId");
migrationBuilder.CreateIndex(
name: "IX_identity_users_TenantId_Account",
table: "identity_users",
columns: new[] { "TenantId", "Account" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_mini_users_TenantId",
table: "mini_users",
column: "TenantId");
migrationBuilder.CreateIndex(
name: "IX_mini_users_TenantId_OpenId",
table: "mini_users",
columns: new[] { "TenantId", "OpenId" },
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "identity_users");
migrationBuilder.DropTable(
name: "mini_users");
}
}
}

View File

@@ -1,581 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace TakeoutSaaS.Infrastructure.Identity.Migrations
{
/// <inheritdoc />
public partial class AddEntityComments : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterTable(
name: "mini_users",
comment: "小程序用户实体。");
migrationBuilder.AlterTable(
name: "identity_users",
comment: "管理后台账户实体(平台管理员、租户管理员或商户员工)。");
migrationBuilder.AlterColumn<Guid>(
name: "UpdatedBy",
table: "mini_users",
type: "uuid",
nullable: true,
comment: "最后更新人用户标识,匿名或系统操作时为 null。",
oldClrType: typeof(Guid),
oldType: "uuid",
oldNullable: true);
migrationBuilder.AlterColumn<DateTime>(
name: "UpdatedAt",
table: "mini_users",
type: "timestamp with time zone",
nullable: true,
comment: "最近一次更新时间UTC从未更新时为 null。",
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone",
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "UnionId",
table: "mini_users",
type: "character varying(128)",
maxLength: 128,
nullable: true,
comment: "微信 UnionId可能为空。",
oldClrType: typeof(string),
oldType: "character varying(128)",
oldMaxLength: 128,
oldNullable: true);
migrationBuilder.AlterColumn<Guid>(
name: "TenantId",
table: "mini_users",
type: "uuid",
nullable: false,
comment: "所属租户 ID。",
oldClrType: typeof(Guid),
oldType: "uuid");
migrationBuilder.AlterColumn<string>(
name: "OpenId",
table: "mini_users",
type: "character varying(128)",
maxLength: 128,
nullable: false,
comment: "微信 OpenId。",
oldClrType: typeof(string),
oldType: "character varying(128)",
oldMaxLength: 128);
migrationBuilder.AlterColumn<string>(
name: "Nickname",
table: "mini_users",
type: "character varying(64)",
maxLength: 64,
nullable: false,
comment: "昵称。",
oldClrType: typeof(string),
oldType: "character varying(64)",
oldMaxLength: 64);
migrationBuilder.AlterColumn<Guid>(
name: "DeletedBy",
table: "mini_users",
type: "uuid",
nullable: true,
comment: "删除人用户标识(软删除),未删除时为 null。",
oldClrType: typeof(Guid),
oldType: "uuid",
oldNullable: true);
migrationBuilder.AlterColumn<DateTime>(
name: "DeletedAt",
table: "mini_users",
type: "timestamp with time zone",
nullable: true,
comment: "软删除时间UTC未删除时为 null。",
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone",
oldNullable: true);
migrationBuilder.AlterColumn<Guid>(
name: "CreatedBy",
table: "mini_users",
type: "uuid",
nullable: true,
comment: "创建人用户标识,匿名或系统操作时为 null。",
oldClrType: typeof(Guid),
oldType: "uuid",
oldNullable: true);
migrationBuilder.AlterColumn<DateTime>(
name: "CreatedAt",
table: "mini_users",
type: "timestamp with time zone",
nullable: false,
comment: "创建时间UTC。",
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone");
migrationBuilder.AlterColumn<string>(
name: "Avatar",
table: "mini_users",
type: "character varying(256)",
maxLength: 256,
nullable: true,
comment: "头像地址。",
oldClrType: typeof(string),
oldType: "character varying(256)",
oldMaxLength: 256,
oldNullable: true);
migrationBuilder.AlterColumn<Guid>(
name: "Id",
table: "mini_users",
type: "uuid",
nullable: false,
comment: "实体唯一标识。",
oldClrType: typeof(Guid),
oldType: "uuid");
migrationBuilder.AlterColumn<Guid>(
name: "UpdatedBy",
table: "identity_users",
type: "uuid",
nullable: true,
comment: "最后更新人用户标识,匿名或系统操作时为 null。",
oldClrType: typeof(Guid),
oldType: "uuid",
oldNullable: true);
migrationBuilder.AlterColumn<DateTime>(
name: "UpdatedAt",
table: "identity_users",
type: "timestamp with time zone",
nullable: true,
comment: "最近一次更新时间UTC从未更新时为 null。",
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone",
oldNullable: true);
migrationBuilder.AlterColumn<Guid>(
name: "TenantId",
table: "identity_users",
type: "uuid",
nullable: false,
comment: "所属租户 ID。",
oldClrType: typeof(Guid),
oldType: "uuid");
migrationBuilder.AlterColumn<string>(
name: "Roles",
table: "identity_users",
type: "text",
nullable: false,
comment: "角色集合。",
oldClrType: typeof(string),
oldType: "text");
migrationBuilder.AlterColumn<string>(
name: "Permissions",
table: "identity_users",
type: "text",
nullable: false,
comment: "权限集合。",
oldClrType: typeof(string),
oldType: "text");
migrationBuilder.AlterColumn<string>(
name: "PasswordHash",
table: "identity_users",
type: "character varying(256)",
maxLength: 256,
nullable: false,
comment: "密码哈希。",
oldClrType: typeof(string),
oldType: "character varying(256)",
oldMaxLength: 256);
migrationBuilder.AlterColumn<Guid>(
name: "MerchantId",
table: "identity_users",
type: "uuid",
nullable: true,
comment: "所属商户(平台管理员为空)。",
oldClrType: typeof(Guid),
oldType: "uuid",
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "DisplayName",
table: "identity_users",
type: "character varying(64)",
maxLength: 64,
nullable: false,
comment: "展示名称。",
oldClrType: typeof(string),
oldType: "character varying(64)",
oldMaxLength: 64);
migrationBuilder.AlterColumn<Guid>(
name: "DeletedBy",
table: "identity_users",
type: "uuid",
nullable: true,
comment: "删除人用户标识(软删除),未删除时为 null。",
oldClrType: typeof(Guid),
oldType: "uuid",
oldNullable: true);
migrationBuilder.AlterColumn<DateTime>(
name: "DeletedAt",
table: "identity_users",
type: "timestamp with time zone",
nullable: true,
comment: "软删除时间UTC未删除时为 null。",
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone",
oldNullable: true);
migrationBuilder.AlterColumn<Guid>(
name: "CreatedBy",
table: "identity_users",
type: "uuid",
nullable: true,
comment: "创建人用户标识,匿名或系统操作时为 null。",
oldClrType: typeof(Guid),
oldType: "uuid",
oldNullable: true);
migrationBuilder.AlterColumn<DateTime>(
name: "CreatedAt",
table: "identity_users",
type: "timestamp with time zone",
nullable: false,
comment: "创建时间UTC。",
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone");
migrationBuilder.AlterColumn<string>(
name: "Avatar",
table: "identity_users",
type: "character varying(256)",
maxLength: 256,
nullable: true,
comment: "头像地址。",
oldClrType: typeof(string),
oldType: "character varying(256)",
oldMaxLength: 256,
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "Account",
table: "identity_users",
type: "character varying(64)",
maxLength: 64,
nullable: false,
comment: "登录账号。",
oldClrType: typeof(string),
oldType: "character varying(64)",
oldMaxLength: 64);
migrationBuilder.AlterColumn<Guid>(
name: "Id",
table: "identity_users",
type: "uuid",
nullable: false,
comment: "实体唯一标识。",
oldClrType: typeof(Guid),
oldType: "uuid");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterTable(
name: "mini_users",
oldComment: "小程序用户实体。");
migrationBuilder.AlterTable(
name: "identity_users",
oldComment: "管理后台账户实体(平台管理员、租户管理员或商户员工)。");
migrationBuilder.AlterColumn<Guid>(
name: "UpdatedBy",
table: "mini_users",
type: "uuid",
nullable: true,
oldClrType: typeof(Guid),
oldType: "uuid",
oldNullable: true,
oldComment: "最后更新人用户标识,匿名或系统操作时为 null。");
migrationBuilder.AlterColumn<DateTime>(
name: "UpdatedAt",
table: "mini_users",
type: "timestamp with time zone",
nullable: true,
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone",
oldNullable: true,
oldComment: "最近一次更新时间UTC从未更新时为 null。");
migrationBuilder.AlterColumn<string>(
name: "UnionId",
table: "mini_users",
type: "character varying(128)",
maxLength: 128,
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(128)",
oldMaxLength: 128,
oldNullable: true,
oldComment: "微信 UnionId可能为空。");
migrationBuilder.AlterColumn<Guid>(
name: "TenantId",
table: "mini_users",
type: "uuid",
nullable: false,
oldClrType: typeof(Guid),
oldType: "uuid",
oldComment: "所属租户 ID。");
migrationBuilder.AlterColumn<string>(
name: "OpenId",
table: "mini_users",
type: "character varying(128)",
maxLength: 128,
nullable: false,
oldClrType: typeof(string),
oldType: "character varying(128)",
oldMaxLength: 128,
oldComment: "微信 OpenId。");
migrationBuilder.AlterColumn<string>(
name: "Nickname",
table: "mini_users",
type: "character varying(64)",
maxLength: 64,
nullable: false,
oldClrType: typeof(string),
oldType: "character varying(64)",
oldMaxLength: 64,
oldComment: "昵称。");
migrationBuilder.AlterColumn<Guid>(
name: "DeletedBy",
table: "mini_users",
type: "uuid",
nullable: true,
oldClrType: typeof(Guid),
oldType: "uuid",
oldNullable: true,
oldComment: "删除人用户标识(软删除),未删除时为 null。");
migrationBuilder.AlterColumn<DateTime>(
name: "DeletedAt",
table: "mini_users",
type: "timestamp with time zone",
nullable: true,
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone",
oldNullable: true,
oldComment: "软删除时间UTC未删除时为 null。");
migrationBuilder.AlterColumn<Guid>(
name: "CreatedBy",
table: "mini_users",
type: "uuid",
nullable: true,
oldClrType: typeof(Guid),
oldType: "uuid",
oldNullable: true,
oldComment: "创建人用户标识,匿名或系统操作时为 null。");
migrationBuilder.AlterColumn<DateTime>(
name: "CreatedAt",
table: "mini_users",
type: "timestamp with time zone",
nullable: false,
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone",
oldComment: "创建时间UTC。");
migrationBuilder.AlterColumn<string>(
name: "Avatar",
table: "mini_users",
type: "character varying(256)",
maxLength: 256,
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(256)",
oldMaxLength: 256,
oldNullable: true,
oldComment: "头像地址。");
migrationBuilder.AlterColumn<Guid>(
name: "Id",
table: "mini_users",
type: "uuid",
nullable: false,
oldClrType: typeof(Guid),
oldType: "uuid",
oldComment: "实体唯一标识。");
migrationBuilder.AlterColumn<Guid>(
name: "UpdatedBy",
table: "identity_users",
type: "uuid",
nullable: true,
oldClrType: typeof(Guid),
oldType: "uuid",
oldNullable: true,
oldComment: "最后更新人用户标识,匿名或系统操作时为 null。");
migrationBuilder.AlterColumn<DateTime>(
name: "UpdatedAt",
table: "identity_users",
type: "timestamp with time zone",
nullable: true,
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone",
oldNullable: true,
oldComment: "最近一次更新时间UTC从未更新时为 null。");
migrationBuilder.AlterColumn<Guid>(
name: "TenantId",
table: "identity_users",
type: "uuid",
nullable: false,
oldClrType: typeof(Guid),
oldType: "uuid",
oldComment: "所属租户 ID。");
migrationBuilder.AlterColumn<string>(
name: "Roles",
table: "identity_users",
type: "text",
nullable: false,
oldClrType: typeof(string),
oldType: "text",
oldComment: "角色集合。");
migrationBuilder.AlterColumn<string>(
name: "Permissions",
table: "identity_users",
type: "text",
nullable: false,
oldClrType: typeof(string),
oldType: "text",
oldComment: "权限集合。");
migrationBuilder.AlterColumn<string>(
name: "PasswordHash",
table: "identity_users",
type: "character varying(256)",
maxLength: 256,
nullable: false,
oldClrType: typeof(string),
oldType: "character varying(256)",
oldMaxLength: 256,
oldComment: "密码哈希。");
migrationBuilder.AlterColumn<Guid>(
name: "MerchantId",
table: "identity_users",
type: "uuid",
nullable: true,
oldClrType: typeof(Guid),
oldType: "uuid",
oldNullable: true,
oldComment: "所属商户(平台管理员为空)。");
migrationBuilder.AlterColumn<string>(
name: "DisplayName",
table: "identity_users",
type: "character varying(64)",
maxLength: 64,
nullable: false,
oldClrType: typeof(string),
oldType: "character varying(64)",
oldMaxLength: 64,
oldComment: "展示名称。");
migrationBuilder.AlterColumn<Guid>(
name: "DeletedBy",
table: "identity_users",
type: "uuid",
nullable: true,
oldClrType: typeof(Guid),
oldType: "uuid",
oldNullable: true,
oldComment: "删除人用户标识(软删除),未删除时为 null。");
migrationBuilder.AlterColumn<DateTime>(
name: "DeletedAt",
table: "identity_users",
type: "timestamp with time zone",
nullable: true,
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone",
oldNullable: true,
oldComment: "软删除时间UTC未删除时为 null。");
migrationBuilder.AlterColumn<Guid>(
name: "CreatedBy",
table: "identity_users",
type: "uuid",
nullable: true,
oldClrType: typeof(Guid),
oldType: "uuid",
oldNullable: true,
oldComment: "创建人用户标识,匿名或系统操作时为 null。");
migrationBuilder.AlterColumn<DateTime>(
name: "CreatedAt",
table: "identity_users",
type: "timestamp with time zone",
nullable: false,
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone",
oldComment: "创建时间UTC。");
migrationBuilder.AlterColumn<string>(
name: "Avatar",
table: "identity_users",
type: "character varying(256)",
maxLength: 256,
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(256)",
oldMaxLength: 256,
oldNullable: true,
oldComment: "头像地址。");
migrationBuilder.AlterColumn<string>(
name: "Account",
table: "identity_users",
type: "character varying(64)",
maxLength: 64,
nullable: false,
oldClrType: typeof(string),
oldType: "character varying(64)",
oldMaxLength: 64,
oldComment: "登录账号。");
migrationBuilder.AlterColumn<Guid>(
name: "Id",
table: "identity_users",
type: "uuid",
nullable: false,
oldClrType: typeof(Guid),
oldType: "uuid",
oldComment: "实体唯一标识。");
}
}
}

View File

@@ -39,12 +39,12 @@ public sealed class SeedUserOptions
/// <summary>
/// 所属租户 ID。
/// </summary>
public Guid TenantId { get; set; }
public long TenantId { get; set; }
/// <summary>
/// 所属商户 ID平台管理员为空
/// </summary>
public Guid? MerchantId { get; set; }
public long? MerchantId { get; set; }
/// <summary>
/// 角色集合。

View File

@@ -22,6 +22,6 @@ public sealed class EfIdentityUserRepository : IIdentityUserRepository
public Task<IdentityUser?> FindByAccountAsync(string account, CancellationToken cancellationToken = default)
=> _dbContext.IdentityUsers.AsNoTracking().FirstOrDefaultAsync(x => x.Account == account, cancellationToken);
public Task<IdentityUser?> FindByIdAsync(Guid userId, CancellationToken cancellationToken = default)
public Task<IdentityUser?> FindByIdAsync(long userId, CancellationToken cancellationToken = default)
=> _dbContext.IdentityUsers.AsNoTracking().FirstOrDefaultAsync(x => x.Id == userId, cancellationToken);
}

View File

@@ -22,17 +22,17 @@ public sealed class EfMiniUserRepository : IMiniUserRepository
public Task<MiniUser?> FindByOpenIdAsync(string openId, CancellationToken cancellationToken = default)
=> _dbContext.MiniUsers.AsNoTracking().FirstOrDefaultAsync(x => x.OpenId == openId, cancellationToken);
public Task<MiniUser?> FindByIdAsync(Guid id, CancellationToken cancellationToken = default)
public Task<MiniUser?> FindByIdAsync(long id, CancellationToken cancellationToken = default)
=> _dbContext.MiniUsers.AsNoTracking().FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
public async Task<MiniUser> CreateOrUpdateAsync(string openId, string? unionId, string? nickname, string? avatar, Guid tenantId, CancellationToken cancellationToken = default)
public async Task<MiniUser> CreateOrUpdateAsync(string openId, string? unionId, string? nickname, string? avatar, long tenantId, CancellationToken cancellationToken = default)
{
var user = await _dbContext.MiniUsers.FirstOrDefaultAsync(x => x.OpenId == openId, cancellationToken);
if (user == null)
{
user = new MiniUser
{
Id = Guid.NewGuid(),
Id = 0,
OpenId = openId,
UnionId = unionId,
Nickname = nickname ?? "小程序用户",

View File

@@ -42,7 +42,7 @@ public sealed class IdentityDataSeeder(IServiceProvider serviceProvider, ILogger
{
user = new DomainIdentityUser
{
Id = Guid.NewGuid(),
Id = 0,
Account = userOptions.Account,
DisplayName = userOptions.DisplayName,
TenantId = userOptions.TenantId,
@@ -80,7 +80,7 @@ public sealed class IdentityDataSeeder(IServiceProvider serviceProvider, ILogger
.Select(v => v.Trim())
.Distinct(StringComparer.OrdinalIgnoreCase)];
private static IDisposable EnterTenantScope(ITenantContextAccessor accessor, Guid tenantId)
private static IDisposable EnterTenantScope(ITenantContextAccessor accessor, long tenantId)
{
var previous = accessor.Current;
accessor.Current = new TenantContext(tenantId, null, "admin-seed");

View File

@@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using TakeoutSaaS.Domain.Identity.Entities;
using TakeoutSaaS.Infrastructure.Common.Persistence;
using TakeoutSaaS.Shared.Abstractions.Ids;
using TakeoutSaaS.Shared.Abstractions.Security;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
@@ -16,8 +17,9 @@ namespace TakeoutSaaS.Infrastructure.Identity.Persistence;
public sealed class IdentityDbContext(
DbContextOptions<IdentityDbContext> options,
ITenantProvider tenantProvider,
ICurrentUserAccessor? currentUserAccessor = null)
: TenantAwareDbContext(options, tenantProvider, currentUserAccessor)
ICurrentUserAccessor? currentUserAccessor = null,
IIdGenerator? idGenerator = null)
: TenantAwareDbContext(options, tenantProvider, currentUserAccessor, idGenerator)
{
/// <summary>
/// 管理后台用户集合。

View File

@@ -26,7 +26,7 @@ public sealed class RedisRefreshTokenStore : IRefreshTokenStore
_options = options.Value;
}
public async Task<RefreshTokenDescriptor> IssueAsync(Guid userId, DateTime expiresAt, CancellationToken cancellationToken = default)
public async Task<RefreshTokenDescriptor> IssueAsync(long userId, DateTime expiresAt, CancellationToken cancellationToken = default)
{
var token = Convert.ToBase64String(RandomNumberGenerator.GetBytes(48));
var descriptor = new RefreshTokenDescriptor(token, userId, expiresAt, false);

View File

@@ -9,11 +9,11 @@ using TakeoutSaaS.Infrastructure.Dictionary.Persistence;
#nullable disable
namespace TakeoutSaaS.Infrastructure.Dictionary.Migrations
namespace TakeoutSaaS.Infrastructure.Migrations.DictionaryDb
{
[DbContext(typeof(DictionaryDbContext))]
[Migration("20251201094456_AddEntityComments")]
partial class AddEntityComments
[Migration("20251202005247_InitSnowflake_Dictionary")]
partial class InitSnowflake_Dictionary
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
@@ -27,11 +27,13 @@ namespace TakeoutSaaS.Infrastructure.Dictionary.Migrations
modelBuilder.Entity("TakeoutSaaS.Domain.Dictionary.Entities.DictionaryGroup", b =>
{
b.Property<Guid>("Id")
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnType("bigint")
.HasComment("实体唯一标识。");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(64)
@@ -42,16 +44,16 @@ namespace TakeoutSaaS.Infrastructure.Dictionary.Migrations
.HasColumnType("timestamp with time zone")
.HasComment("创建时间UTC。");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uuid")
b.Property<long?>("CreatedBy")
.HasColumnType("bigint")
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone")
.HasComment("软删除时间UTC未删除时为 null。");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uuid")
b.Property<long?>("DeletedBy")
.HasColumnType("bigint")
.HasComment("删除人用户标识(软删除),未删除时为 null。");
b.Property<string>("Description")
@@ -75,16 +77,16 @@ namespace TakeoutSaaS.Infrastructure.Dictionary.Migrations
.HasColumnType("integer")
.HasComment("分组作用域:系统/业务。");
b.Property<Guid>("TenantId")
.HasColumnType("uuid")
b.Property<long>("TenantId")
.HasColumnType("bigint")
.HasComment("所属租户 ID。");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
.HasComment("最近一次更新时间UTC从未更新时为 null。");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uuid")
b.Property<long?>("UpdatedBy")
.HasColumnType("bigint")
.HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
b.HasKey("Id");
@@ -102,25 +104,27 @@ namespace TakeoutSaaS.Infrastructure.Dictionary.Migrations
modelBuilder.Entity("TakeoutSaaS.Domain.Dictionary.Entities.DictionaryItem", b =>
{
b.Property<Guid>("Id")
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnType("bigint")
.HasComment("实体唯一标识。");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasComment("创建时间UTC。");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uuid")
b.Property<long?>("CreatedBy")
.HasColumnType("bigint")
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone")
.HasComment("软删除时间UTC未删除时为 null。");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uuid")
b.Property<long?>("DeletedBy")
.HasColumnType("bigint")
.HasComment("删除人用户标识(软删除),未删除时为 null。");
b.Property<string>("Description")
@@ -128,8 +132,8 @@ namespace TakeoutSaaS.Infrastructure.Dictionary.Migrations
.HasColumnType("character varying(512)")
.HasComment("描述信息。");
b.Property<Guid>("GroupId")
.HasColumnType("uuid")
b.Property<long>("GroupId")
.HasColumnType("bigint")
.HasComment("关联分组 ID。");
b.Property<bool>("IsDefault")
@@ -154,16 +158,16 @@ namespace TakeoutSaaS.Infrastructure.Dictionary.Migrations
.HasDefaultValue(100)
.HasComment("排序值,越小越靠前。");
b.Property<Guid>("TenantId")
.HasColumnType("uuid")
b.Property<long>("TenantId")
.HasColumnType("bigint")
.HasComment("所属租户 ID。");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
.HasComment("最近一次更新时间UTC从未更新时为 null。");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uuid")
b.Property<long?>("UpdatedBy")
.HasColumnType("bigint")
.HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
b.Property<string>("Value")

View File

@@ -0,0 +1,106 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace TakeoutSaaS.Infrastructure.Migrations.DictionaryDb
{
/// <inheritdoc />
public partial class InitSnowflake_Dictionary : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "dictionary_groups",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false, comment: "实体唯一标识。")
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
Code = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false, comment: "分组编码(唯一)。"),
Name = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false, comment: "分组名称。"),
Scope = table.Column<int>(type: "integer", nullable: false, comment: "分组作用域:系统/业务。"),
Description = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: true, comment: "描述信息。"),
IsEnabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true, comment: "是否启用。"),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, comment: "创建时间UTC。"),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, comment: "最近一次更新时间UTC从未更新时为 null。"),
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, comment: "软删除时间UTC未删除时为 null。"),
CreatedBy = table.Column<long>(type: "bigint", nullable: true, comment: "创建人用户标识,匿名或系统操作时为 null。"),
UpdatedBy = table.Column<long>(type: "bigint", nullable: true, comment: "最后更新人用户标识,匿名或系统操作时为 null。"),
DeletedBy = table.Column<long>(type: "bigint", nullable: true, comment: "删除人用户标识(软删除),未删除时为 null。"),
TenantId = table.Column<long>(type: "bigint", nullable: false, comment: "所属租户 ID。")
},
constraints: table =>
{
table.PrimaryKey("PK_dictionary_groups", x => x.Id);
},
comment: "参数字典分组(系统参数、业务参数)。");
migrationBuilder.CreateTable(
name: "dictionary_items",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false, comment: "实体唯一标识。")
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
GroupId = table.Column<long>(type: "bigint", nullable: false, comment: "关联分组 ID。"),
Key = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false, comment: "字典项键。"),
Value = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false, comment: "字典项值。"),
IsDefault = table.Column<bool>(type: "boolean", nullable: false, comment: "是否默认项。"),
IsEnabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true, comment: "是否启用。"),
SortOrder = table.Column<int>(type: "integer", nullable: false, defaultValue: 100, comment: "排序值,越小越靠前。"),
Description = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: true, comment: "描述信息。"),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, comment: "创建时间UTC。"),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, comment: "最近一次更新时间UTC从未更新时为 null。"),
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, comment: "软删除时间UTC未删除时为 null。"),
CreatedBy = table.Column<long>(type: "bigint", nullable: true, comment: "创建人用户标识,匿名或系统操作时为 null。"),
UpdatedBy = table.Column<long>(type: "bigint", nullable: true, comment: "最后更新人用户标识,匿名或系统操作时为 null。"),
DeletedBy = table.Column<long>(type: "bigint", nullable: true, comment: "删除人用户标识(软删除),未删除时为 null。"),
TenantId = table.Column<long>(type: "bigint", nullable: false, comment: "所属租户 ID。")
},
constraints: table =>
{
table.PrimaryKey("PK_dictionary_items", x => x.Id);
table.ForeignKey(
name: "FK_dictionary_items_dictionary_groups_GroupId",
column: x => x.GroupId,
principalTable: "dictionary_groups",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
},
comment: "参数字典项。");
migrationBuilder.CreateIndex(
name: "IX_dictionary_groups_TenantId",
table: "dictionary_groups",
column: "TenantId");
migrationBuilder.CreateIndex(
name: "IX_dictionary_groups_TenantId_Code",
table: "dictionary_groups",
columns: new[] { "TenantId", "Code" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_dictionary_items_GroupId_Key",
table: "dictionary_items",
columns: new[] { "GroupId", "Key" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_dictionary_items_TenantId",
table: "dictionary_items",
column: "TenantId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "dictionary_items");
migrationBuilder.DropTable(
name: "dictionary_groups");
}
}
}

View File

@@ -8,7 +8,7 @@ using TakeoutSaaS.Infrastructure.Dictionary.Persistence;
#nullable disable
namespace TakeoutSaaS.Infrastructure.Dictionary.Migrations
namespace TakeoutSaaS.Infrastructure.Migrations.DictionaryDb
{
[DbContext(typeof(DictionaryDbContext))]
partial class DictionaryDbContextModelSnapshot : ModelSnapshot
@@ -24,11 +24,13 @@ namespace TakeoutSaaS.Infrastructure.Dictionary.Migrations
modelBuilder.Entity("TakeoutSaaS.Domain.Dictionary.Entities.DictionaryGroup", b =>
{
b.Property<Guid>("Id")
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnType("bigint")
.HasComment("实体唯一标识。");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(64)
@@ -39,16 +41,16 @@ namespace TakeoutSaaS.Infrastructure.Dictionary.Migrations
.HasColumnType("timestamp with time zone")
.HasComment("创建时间UTC。");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uuid")
b.Property<long?>("CreatedBy")
.HasColumnType("bigint")
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone")
.HasComment("软删除时间UTC未删除时为 null。");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uuid")
b.Property<long?>("DeletedBy")
.HasColumnType("bigint")
.HasComment("删除人用户标识(软删除),未删除时为 null。");
b.Property<string>("Description")
@@ -72,16 +74,16 @@ namespace TakeoutSaaS.Infrastructure.Dictionary.Migrations
.HasColumnType("integer")
.HasComment("分组作用域:系统/业务。");
b.Property<Guid>("TenantId")
.HasColumnType("uuid")
b.Property<long>("TenantId")
.HasColumnType("bigint")
.HasComment("所属租户 ID。");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
.HasComment("最近一次更新时间UTC从未更新时为 null。");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uuid")
b.Property<long?>("UpdatedBy")
.HasColumnType("bigint")
.HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
b.HasKey("Id");
@@ -99,25 +101,27 @@ namespace TakeoutSaaS.Infrastructure.Dictionary.Migrations
modelBuilder.Entity("TakeoutSaaS.Domain.Dictionary.Entities.DictionaryItem", b =>
{
b.Property<Guid>("Id")
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnType("bigint")
.HasComment("实体唯一标识。");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasComment("创建时间UTC。");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uuid")
b.Property<long?>("CreatedBy")
.HasColumnType("bigint")
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone")
.HasComment("软删除时间UTC未删除时为 null。");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uuid")
b.Property<long?>("DeletedBy")
.HasColumnType("bigint")
.HasComment("删除人用户标识(软删除),未删除时为 null。");
b.Property<string>("Description")
@@ -125,8 +129,8 @@ namespace TakeoutSaaS.Infrastructure.Dictionary.Migrations
.HasColumnType("character varying(512)")
.HasComment("描述信息。");
b.Property<Guid>("GroupId")
.HasColumnType("uuid")
b.Property<long>("GroupId")
.HasColumnType("bigint")
.HasComment("关联分组 ID。");
b.Property<bool>("IsDefault")
@@ -151,16 +155,16 @@ namespace TakeoutSaaS.Infrastructure.Dictionary.Migrations
.HasDefaultValue(100)
.HasComment("排序值,越小越靠前。");
b.Property<Guid>("TenantId")
.HasColumnType("uuid")
b.Property<long>("TenantId")
.HasColumnType("bigint")
.HasComment("所属租户 ID。");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
.HasComment("最近一次更新时间UTC从未更新时为 null。");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uuid")
b.Property<long?>("UpdatedBy")
.HasColumnType("bigint")
.HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
b.Property<string>("Value")

View File

@@ -9,11 +9,11 @@ using TakeoutSaaS.Infrastructure.Identity.Persistence;
#nullable disable
namespace TakeoutSaaS.Infrastructure.Identity.Migrations
namespace TakeoutSaaS.Infrastructure.Migrations.IdentityDb
{
[DbContext(typeof(IdentityDbContext))]
[Migration("20251201094410_AddEntityComments")]
partial class AddEntityComments
[Migration("20251202005226_InitSnowflake_Identity")]
partial class InitSnowflake_Identity
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
@@ -27,11 +27,13 @@ namespace TakeoutSaaS.Infrastructure.Identity.Migrations
modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.IdentityUser", b =>
{
b.Property<Guid>("Id")
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnType("bigint")
.HasComment("实体唯一标识。");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<string>("Account")
.IsRequired()
.HasMaxLength(64)
@@ -47,16 +49,16 @@ namespace TakeoutSaaS.Infrastructure.Identity.Migrations
.HasColumnType("timestamp with time zone")
.HasComment("创建时间UTC。");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uuid")
b.Property<long?>("CreatedBy")
.HasColumnType("bigint")
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone")
.HasComment("软删除时间UTC未删除时为 null。");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uuid")
b.Property<long?>("DeletedBy")
.HasColumnType("bigint")
.HasComment("删除人用户标识(软删除),未删除时为 null。");
b.Property<string>("DisplayName")
@@ -65,8 +67,8 @@ namespace TakeoutSaaS.Infrastructure.Identity.Migrations
.HasColumnType("character varying(64)")
.HasComment("展示名称。");
b.Property<Guid?>("MerchantId")
.HasColumnType("uuid")
b.Property<long?>("MerchantId")
.HasColumnType("bigint")
.HasComment("所属商户(平台管理员为空)。");
b.Property<string>("PasswordHash")
@@ -85,16 +87,16 @@ namespace TakeoutSaaS.Infrastructure.Identity.Migrations
.HasColumnType("text")
.HasComment("角色集合。");
b.Property<Guid>("TenantId")
.HasColumnType("uuid")
b.Property<long>("TenantId")
.HasColumnType("bigint")
.HasComment("所属租户 ID。");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
.HasComment("最近一次更新时间UTC从未更新时为 null。");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uuid")
b.Property<long?>("UpdatedBy")
.HasColumnType("bigint")
.HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
b.HasKey("Id");
@@ -112,11 +114,13 @@ namespace TakeoutSaaS.Infrastructure.Identity.Migrations
modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.MiniUser", b =>
{
b.Property<Guid>("Id")
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnType("bigint")
.HasComment("实体唯一标识。");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<string>("Avatar")
.HasMaxLength(256)
.HasColumnType("character varying(256)")
@@ -126,16 +130,16 @@ namespace TakeoutSaaS.Infrastructure.Identity.Migrations
.HasColumnType("timestamp with time zone")
.HasComment("创建时间UTC。");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uuid")
b.Property<long?>("CreatedBy")
.HasColumnType("bigint")
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone")
.HasComment("软删除时间UTC未删除时为 null。");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uuid")
b.Property<long?>("DeletedBy")
.HasColumnType("bigint")
.HasComment("删除人用户标识(软删除),未删除时为 null。");
b.Property<string>("Nickname")
@@ -150,8 +154,8 @@ namespace TakeoutSaaS.Infrastructure.Identity.Migrations
.HasColumnType("character varying(128)")
.HasComment("微信 OpenId。");
b.Property<Guid>("TenantId")
.HasColumnType("uuid")
b.Property<long>("TenantId")
.HasColumnType("bigint")
.HasComment("所属租户 ID。");
b.Property<string>("UnionId")
@@ -163,8 +167,8 @@ namespace TakeoutSaaS.Infrastructure.Identity.Migrations
.HasColumnType("timestamp with time zone")
.HasComment("最近一次更新时间UTC从未更新时为 null。");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uuid")
b.Property<long?>("UpdatedBy")
.HasColumnType("bigint")
.HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
b.HasKey("Id");

View File

@@ -0,0 +1,99 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace TakeoutSaaS.Infrastructure.Migrations.IdentityDb
{
/// <inheritdoc />
public partial class InitSnowflake_Identity : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "identity_users",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false, comment: "实体唯一标识。")
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
Account = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false, comment: "登录账号。"),
DisplayName = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false, comment: "展示名称。"),
PasswordHash = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false, comment: "密码哈希。"),
MerchantId = table.Column<long>(type: "bigint", nullable: true, comment: "所属商户(平台管理员为空)。"),
Roles = table.Column<string>(type: "text", nullable: false, comment: "角色集合。"),
Permissions = table.Column<string>(type: "text", nullable: false, comment: "权限集合。"),
Avatar = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true, comment: "头像地址。"),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, comment: "创建时间UTC。"),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, comment: "最近一次更新时间UTC从未更新时为 null。"),
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, comment: "软删除时间UTC未删除时为 null。"),
CreatedBy = table.Column<long>(type: "bigint", nullable: true, comment: "创建人用户标识,匿名或系统操作时为 null。"),
UpdatedBy = table.Column<long>(type: "bigint", nullable: true, comment: "最后更新人用户标识,匿名或系统操作时为 null。"),
DeletedBy = table.Column<long>(type: "bigint", nullable: true, comment: "删除人用户标识(软删除),未删除时为 null。"),
TenantId = table.Column<long>(type: "bigint", nullable: false, comment: "所属租户 ID。")
},
constraints: table =>
{
table.PrimaryKey("PK_identity_users", x => x.Id);
},
comment: "管理后台账户实体(平台管理员、租户管理员或商户员工)。");
migrationBuilder.CreateTable(
name: "mini_users",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false, comment: "实体唯一标识。")
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
OpenId = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false, comment: "微信 OpenId。"),
UnionId = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true, comment: "微信 UnionId可能为空。"),
Nickname = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false, comment: "昵称。"),
Avatar = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true, comment: "头像地址。"),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, comment: "创建时间UTC。"),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, comment: "最近一次更新时间UTC从未更新时为 null。"),
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, comment: "软删除时间UTC未删除时为 null。"),
CreatedBy = table.Column<long>(type: "bigint", nullable: true, comment: "创建人用户标识,匿名或系统操作时为 null。"),
UpdatedBy = table.Column<long>(type: "bigint", nullable: true, comment: "最后更新人用户标识,匿名或系统操作时为 null。"),
DeletedBy = table.Column<long>(type: "bigint", nullable: true, comment: "删除人用户标识(软删除),未删除时为 null。"),
TenantId = table.Column<long>(type: "bigint", nullable: false, comment: "所属租户 ID。")
},
constraints: table =>
{
table.PrimaryKey("PK_mini_users", x => x.Id);
},
comment: "小程序用户实体。");
migrationBuilder.CreateIndex(
name: "IX_identity_users_TenantId",
table: "identity_users",
column: "TenantId");
migrationBuilder.CreateIndex(
name: "IX_identity_users_TenantId_Account",
table: "identity_users",
columns: new[] { "TenantId", "Account" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_mini_users_TenantId",
table: "mini_users",
column: "TenantId");
migrationBuilder.CreateIndex(
name: "IX_mini_users_TenantId_OpenId",
table: "mini_users",
columns: new[] { "TenantId", "OpenId" },
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "identity_users");
migrationBuilder.DropTable(
name: "mini_users");
}
}
}

View File

@@ -8,7 +8,7 @@ using TakeoutSaaS.Infrastructure.Identity.Persistence;
#nullable disable
namespace TakeoutSaaS.Infrastructure.Identity.Migrations
namespace TakeoutSaaS.Infrastructure.Migrations.IdentityDb
{
[DbContext(typeof(IdentityDbContext))]
partial class IdentityDbContextModelSnapshot : ModelSnapshot
@@ -24,11 +24,13 @@ namespace TakeoutSaaS.Infrastructure.Identity.Migrations
modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.IdentityUser", b =>
{
b.Property<Guid>("Id")
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnType("bigint")
.HasComment("实体唯一标识。");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<string>("Account")
.IsRequired()
.HasMaxLength(64)
@@ -44,16 +46,16 @@ namespace TakeoutSaaS.Infrastructure.Identity.Migrations
.HasColumnType("timestamp with time zone")
.HasComment("创建时间UTC。");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uuid")
b.Property<long?>("CreatedBy")
.HasColumnType("bigint")
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone")
.HasComment("软删除时间UTC未删除时为 null。");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uuid")
b.Property<long?>("DeletedBy")
.HasColumnType("bigint")
.HasComment("删除人用户标识(软删除),未删除时为 null。");
b.Property<string>("DisplayName")
@@ -62,8 +64,8 @@ namespace TakeoutSaaS.Infrastructure.Identity.Migrations
.HasColumnType("character varying(64)")
.HasComment("展示名称。");
b.Property<Guid?>("MerchantId")
.HasColumnType("uuid")
b.Property<long?>("MerchantId")
.HasColumnType("bigint")
.HasComment("所属商户(平台管理员为空)。");
b.Property<string>("PasswordHash")
@@ -82,16 +84,16 @@ namespace TakeoutSaaS.Infrastructure.Identity.Migrations
.HasColumnType("text")
.HasComment("角色集合。");
b.Property<Guid>("TenantId")
.HasColumnType("uuid")
b.Property<long>("TenantId")
.HasColumnType("bigint")
.HasComment("所属租户 ID。");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
.HasComment("最近一次更新时间UTC从未更新时为 null。");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uuid")
b.Property<long?>("UpdatedBy")
.HasColumnType("bigint")
.HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
b.HasKey("Id");
@@ -109,11 +111,13 @@ namespace TakeoutSaaS.Infrastructure.Identity.Migrations
modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.MiniUser", b =>
{
b.Property<Guid>("Id")
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnType("bigint")
.HasComment("实体唯一标识。");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<string>("Avatar")
.HasMaxLength(256)
.HasColumnType("character varying(256)")
@@ -123,16 +127,16 @@ namespace TakeoutSaaS.Infrastructure.Identity.Migrations
.HasColumnType("timestamp with time zone")
.HasComment("创建时间UTC。");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uuid")
b.Property<long?>("CreatedBy")
.HasColumnType("bigint")
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone")
.HasComment("软删除时间UTC未删除时为 null。");
b.Property<Guid?>("DeletedBy")
.HasColumnType("uuid")
b.Property<long?>("DeletedBy")
.HasColumnType("bigint")
.HasComment("删除人用户标识(软删除),未删除时为 null。");
b.Property<string>("Nickname")
@@ -147,8 +151,8 @@ namespace TakeoutSaaS.Infrastructure.Identity.Migrations
.HasColumnType("character varying(128)")
.HasComment("微信 OpenId。");
b.Property<Guid>("TenantId")
.HasColumnType("uuid")
b.Property<long>("TenantId")
.HasColumnType("bigint")
.HasComment("所属租户 ID。");
b.Property<string>("UnionId")
@@ -160,8 +164,8 @@ namespace TakeoutSaaS.Infrastructure.Identity.Migrations
.HasColumnType("timestamp with time zone")
.HasComment("最近一次更新时间UTC从未更新时为 null。");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uuid")
b.Property<long?>("UpdatedBy")
.HasColumnType("bigint")
.HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
b.HasKey("Id");

View File

@@ -23,5 +23,6 @@
<ProjectReference Include="..\..\Domain\TakeoutSaaS.Domain\TakeoutSaaS.Domain.csproj" />
<ProjectReference Include="..\..\Application\TakeoutSaaS.Application\TakeoutSaaS.Application.csproj" />
<ProjectReference Include="..\..\Core\TakeoutSaaS.Shared.Abstractions\TakeoutSaaS.Shared.Abstractions.csproj" />
<ProjectReference Include="..\..\Core\TakeoutSaaS.Shared.Kernel\TakeoutSaaS.Shared.Kernel.csproj" />
</ItemGroup>
</Project>