diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/IdentityDb/20260129111306_PortalizeIdentityRbac.Designer.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/IdentityDb/20260129111306_PortalizeIdentityRbac.Designer.cs
new file mode 100644
index 0000000..15ff5f7
--- /dev/null
+++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/IdentityDb/20260129111306_PortalizeIdentityRbac.Designer.cs
@@ -0,0 +1,956 @@
+//
+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.Migrations.IdentityDb
+{
+ [DbContext(typeof(IdentityDbContext))]
+ [Migration("20260129111306_PortalizeIdentityRbac")]
+ partial class PortalizeIdentityRbac
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "10.0.1")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.InboxState", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Consumed")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("ConsumerId")
+ .HasColumnType("uuid");
+
+ b.Property("Delivered")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("ExpirationTime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("LastSequenceNumber")
+ .HasColumnType("bigint");
+
+ b.Property("LockId")
+ .HasColumnType("uuid");
+
+ b.Property("MessageId")
+ .HasColumnType("uuid");
+
+ b.Property("ReceiveCount")
+ .HasColumnType("integer");
+
+ b.Property("Received")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("RowVersion")
+ .HasColumnType("bytea");
+
+ b.HasKey("Id");
+
+ b.ToTable("InboxState");
+ });
+
+ modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.OutboxMessage", b =>
+ {
+ b.Property("SequenceNumber")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SequenceNumber"));
+
+ b.Property("Body")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("ContentType")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("ConversationId")
+ .HasColumnType("uuid");
+
+ b.Property("CorrelationId")
+ .HasColumnType("uuid");
+
+ b.Property("DestinationAddress")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("EnqueueTime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("ExpirationTime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("FaultAddress")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("Headers")
+ .HasColumnType("text");
+
+ b.Property("InboxConsumerId")
+ .HasColumnType("uuid");
+
+ b.Property("InboxMessageId")
+ .HasColumnType("uuid");
+
+ b.Property("InitiatorId")
+ .HasColumnType("uuid");
+
+ b.Property("MessageId")
+ .HasColumnType("uuid");
+
+ b.Property("MessageType")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("OutboxId")
+ .HasColumnType("uuid");
+
+ b.Property("Properties")
+ .HasColumnType("text");
+
+ b.Property("RequestId")
+ .HasColumnType("uuid");
+
+ b.Property("ResponseAddress")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("SentTime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("SourceAddress")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.HasKey("SequenceNumber");
+
+ b.HasIndex("EnqueueTime");
+
+ b.HasIndex("ExpirationTime");
+
+ b.HasIndex("OutboxId", "SequenceNumber")
+ .IsUnique();
+
+ b.HasIndex("InboxMessageId", "InboxConsumerId", "SequenceNumber")
+ .IsUnique();
+
+ b.ToTable("OutboxMessage");
+ });
+
+ modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.OutboxState", b =>
+ {
+ b.Property("OutboxId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Created")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Delivered")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("LastSequenceNumber")
+ .HasColumnType("bigint");
+
+ b.Property("LockId")
+ .HasColumnType("uuid");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .ValueGeneratedOnAddOrUpdate()
+ .HasColumnType("bytea");
+
+ b.HasKey("OutboxId");
+
+ b.HasIndex("Created");
+
+ b.ToTable("OutboxState");
+ });
+
+ modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.IdentityUser", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasComment("实体唯一标识。");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Account")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasComment("登录账号。");
+
+ b.Property("Avatar")
+ .HasColumnType("text")
+ .HasComment("头像地址。");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("创建时间(UTC)。");
+
+ b.Property("CreatedBy")
+ .HasColumnType("bigint")
+ .HasComment("创建人用户标识,匿名或系统操作时为 null。");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("软删除时间(UTC),未删除时为 null。");
+
+ b.Property("DeletedBy")
+ .HasColumnType("bigint")
+ .HasComment("删除人用户标识(软删除),未删除时为 null。");
+
+ b.Property("DisplayName")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasComment("展示名称。");
+
+ b.Property("Email")
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)")
+ .HasComment("邮箱(租户内唯一)。");
+
+ b.Property("FailedLoginCount")
+ .HasColumnType("integer")
+ .HasComment("登录失败次数。");
+
+ b.Property("LastLoginAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("最近登录时间(UTC)。");
+
+ b.Property("LockedUntil")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("锁定截止时间(UTC)。");
+
+ b.Property("MerchantId")
+ .HasColumnType("bigint")
+ .HasComment("所属商户(平台管理员为空)。");
+
+ b.Property("MustChangePassword")
+ .HasColumnType("boolean")
+ .HasComment("是否强制修改密码。");
+
+ b.Property("PasswordHash")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)")
+ .HasComment("密码哈希。");
+
+ b.Property("Phone")
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasComment("手机号(租户内唯一)。");
+
+ b.Property("Portal")
+ .HasColumnType("integer")
+ .HasComment("账号所属 Portal。");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .IsRequired()
+ .ValueGeneratedOnAddOrUpdate()
+ .HasColumnType("bytea")
+ .HasComment("并发控制字段。");
+
+ b.Property("Status")
+ .HasColumnType("integer")
+ .HasComment("账号状态。");
+
+ b.Property("TenantId")
+ .HasColumnType("bigint")
+ .HasComment("所属租户 ID(Portal=Tenant 时必填;Portal=Admin 时必须为空)。");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("最近一次更新时间(UTC),从未更新时为 null。");
+
+ b.Property("UpdatedBy")
+ .HasColumnType("bigint")
+ .HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Account")
+ .IsUnique()
+ .HasFilter("\"Portal\" = 0");
+
+ b.HasIndex("Email")
+ .IsUnique()
+ .HasFilter("\"Portal\" = 0 AND \"Email\" IS NOT NULL");
+
+ b.HasIndex("Phone")
+ .IsUnique()
+ .HasFilter("\"Portal\" = 0 AND \"Phone\" IS NOT NULL");
+
+ b.HasIndex("TenantId")
+ .HasFilter("\"Portal\" = 1");
+
+ b.HasIndex("TenantId", "Account")
+ .IsUnique()
+ .HasFilter("\"Portal\" = 1");
+
+ b.HasIndex("TenantId", "Email")
+ .IsUnique()
+ .HasFilter("\"Portal\" = 1 AND \"Email\" IS NOT NULL");
+
+ b.HasIndex("TenantId", "Phone")
+ .IsUnique()
+ .HasFilter("\"Portal\" = 1 AND \"Phone\" IS NOT NULL");
+
+ b.ToTable("identity_users", null, t =>
+ {
+ t.HasComment("后台账户实体(按 Portal 区分平台管理员与租户后台账号)。");
+
+ t.HasCheckConstraint("CK_identity_users_Portal_Tenant", "(\"Portal\" = 0 AND \"TenantId\" IS NULL) OR (\"Portal\" = 1 AND \"TenantId\" IS NOT NULL)");
+ });
+ });
+
+ modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.MenuDefinition", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasComment("实体唯一标识。");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("AuthListJson")
+ .HasColumnType("text")
+ .HasComment("按钮权限列表 JSON(存储 MenuAuthItemDto 数组)。");
+
+ b.Property("Component")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)")
+ .HasComment("组件路径(不含 .vue)。");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("创建时间(UTC)。");
+
+ b.Property("CreatedBy")
+ .HasColumnType("bigint")
+ .HasComment("创建人用户标识,匿名或系统操作时为 null。");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("软删除时间(UTC),未删除时为 null。");
+
+ b.Property("DeletedBy")
+ .HasColumnType("bigint")
+ .HasComment("删除人用户标识(软删除),未删除时为 null。");
+
+ b.Property("Icon")
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasComment("图标标识。");
+
+ b.Property("IsIframe")
+ .HasColumnType("boolean")
+ .HasComment("是否 iframe。");
+
+ b.Property("KeepAlive")
+ .HasColumnType("boolean")
+ .HasComment("是否缓存。");
+
+ b.Property("Link")
+ .HasMaxLength(512)
+ .HasColumnType("character varying(512)")
+ .HasComment("外链或 iframe 地址。");
+
+ b.Property("MetaPermissions")
+ .IsRequired()
+ .HasMaxLength(1024)
+ .HasColumnType("character varying(1024)")
+ .HasComment("Meta.permissions(逗号分隔)。");
+
+ b.Property("MetaRoles")
+ .IsRequired()
+ .HasMaxLength(1024)
+ .HasColumnType("character varying(1024)")
+ .HasComment("Meta.roles(逗号分隔)。");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasComment("菜单名称(前端路由 name)。");
+
+ b.Property("ParentId")
+ .HasColumnType("bigint")
+ .HasComment("父级菜单 ID,根节点为 0。");
+
+ b.Property("Path")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)")
+ .HasComment("路由路径。");
+
+ b.Property("Portal")
+ .HasColumnType("integer")
+ .HasComment("菜单所属 Portal。");
+
+ b.Property("RequiredPermissions")
+ .IsRequired()
+ .HasMaxLength(1024)
+ .HasColumnType("character varying(1024)")
+ .HasComment("访问该菜单所需的权限集合(逗号分隔)。");
+
+ b.Property("SortOrder")
+ .HasColumnType("integer")
+ .HasComment("排序。");
+
+ b.Property("Title")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)")
+ .HasComment("标题。");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("最近一次更新时间(UTC),从未更新时为 null。");
+
+ b.Property("UpdatedBy")
+ .HasColumnType("bigint")
+ .HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Portal", "ParentId", "SortOrder");
+
+ b.ToTable("menu_definitions", null, t =>
+ {
+ t.HasComment("后台菜单定义(按 Portal 区分 Admin/Tenant 两套菜单树)。");
+ });
+ });
+
+ modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.MiniUser", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasComment("实体唯一标识。");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Avatar")
+ .HasColumnType("text")
+ .HasComment("头像地址。");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("创建时间(UTC)。");
+
+ b.Property("CreatedBy")
+ .HasColumnType("bigint")
+ .HasComment("创建人用户标识,匿名或系统操作时为 null。");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("软删除时间(UTC),未删除时为 null。");
+
+ b.Property("DeletedBy")
+ .HasColumnType("bigint")
+ .HasComment("删除人用户标识(软删除),未删除时为 null。");
+
+ b.Property("Nickname")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasComment("昵称。");
+
+ b.Property("OpenId")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)")
+ .HasComment("微信 OpenId。");
+
+ b.Property("TenantId")
+ .HasColumnType("bigint")
+ .HasComment("所属租户 ID。");
+
+ b.Property("UnionId")
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)")
+ .HasComment("微信 UnionId,可能为空。");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("最近一次更新时间(UTC),从未更新时为 null。");
+
+ b.Property("UpdatedBy")
+ .HasColumnType("bigint")
+ .HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TenantId");
+
+ b.HasIndex("TenantId", "OpenId")
+ .IsUnique();
+
+ b.ToTable("mini_users", null, t =>
+ {
+ t.HasComment("小程序用户实体。");
+ });
+ });
+
+ modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.Permission", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasComment("实体唯一标识。");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Code")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)")
+ .HasComment("权限编码(全局唯一)。");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("创建时间(UTC)。");
+
+ b.Property("CreatedBy")
+ .HasColumnType("bigint")
+ .HasComment("创建人用户标识,匿名或系统操作时为 null。");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("软删除时间(UTC),未删除时为 null。");
+
+ b.Property("DeletedBy")
+ .HasColumnType("bigint")
+ .HasComment("删除人用户标识(软删除),未删除时为 null。");
+
+ b.Property("Description")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)")
+ .HasComment("描述。");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasComment("权限名称。");
+
+ b.Property("ParentId")
+ .HasColumnType("bigint")
+ .HasComment("父级权限 ID,根节点为 0。");
+
+ b.Property("Portal")
+ .HasColumnType("integer")
+ .HasComment("权限所属 Portal。");
+
+ b.Property("SortOrder")
+ .HasColumnType("integer")
+ .HasComment("排序值,值越小越靠前。");
+
+ b.Property("Type")
+ .IsRequired()
+ .HasMaxLength(16)
+ .HasColumnType("character varying(16)")
+ .HasComment("权限类型(group/leaf)。");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("最近一次更新时间(UTC),从未更新时为 null。");
+
+ b.Property("UpdatedBy")
+ .HasColumnType("bigint")
+ .HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Code")
+ .IsUnique();
+
+ b.HasIndex("Portal", "ParentId", "SortOrder");
+
+ b.ToTable("permissions", null, t =>
+ {
+ t.HasComment("权限定义。");
+ });
+ });
+
+ modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.Role", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasComment("实体唯一标识。");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Code")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasComment("角色编码(租户内唯一)。");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("创建时间(UTC)。");
+
+ b.Property("CreatedBy")
+ .HasColumnType("bigint")
+ .HasComment("创建人用户标识,匿名或系统操作时为 null。");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("软删除时间(UTC),未删除时为 null。");
+
+ b.Property("DeletedBy")
+ .HasColumnType("bigint")
+ .HasComment("删除人用户标识(软删除),未删除时为 null。");
+
+ b.Property("Description")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)")
+ .HasComment("描述。");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasComment("角色名称。");
+
+ b.Property("Portal")
+ .HasColumnType("integer")
+ .HasComment("角色所属 Portal。");
+
+ b.Property("TenantId")
+ .HasColumnType("bigint")
+ .HasComment("所属租户 ID(Portal=Tenant 时必填;Portal=Admin 时必须为空)。");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("最近一次更新时间(UTC),从未更新时为 null。");
+
+ b.Property("UpdatedBy")
+ .HasColumnType("bigint")
+ .HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Code")
+ .IsUnique()
+ .HasFilter("\"Portal\" = 0");
+
+ b.HasIndex("TenantId")
+ .HasFilter("\"Portal\" = 1");
+
+ b.HasIndex("TenantId", "Code")
+ .IsUnique()
+ .HasFilter("\"Portal\" = 1");
+
+ b.ToTable("roles", null, t =>
+ {
+ t.HasComment("角色定义。");
+
+ t.HasCheckConstraint("CK_roles_Portal_Tenant", "(\"Portal\" = 0 AND \"TenantId\" IS NULL) OR (\"Portal\" = 1 AND \"TenantId\" IS NOT NULL)");
+ });
+ });
+
+ modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.RolePermission", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasComment("实体唯一标识。");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("创建时间(UTC)。");
+
+ b.Property("CreatedBy")
+ .HasColumnType("bigint")
+ .HasComment("创建人用户标识,匿名或系统操作时为 null。");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("软删除时间(UTC),未删除时为 null。");
+
+ b.Property("DeletedBy")
+ .HasColumnType("bigint")
+ .HasComment("删除人用户标识(软删除),未删除时为 null。");
+
+ b.Property("PermissionId")
+ .HasColumnType("bigint")
+ .HasComment("权限 ID。");
+
+ b.Property("Portal")
+ .HasColumnType("integer")
+ .HasComment("关系所属 Portal。");
+
+ b.Property("RoleId")
+ .HasColumnType("bigint")
+ .HasComment("角色 ID。");
+
+ b.Property("TenantId")
+ .HasColumnType("bigint")
+ .HasComment("所属租户 ID(Portal=Tenant 时必填;Portal=Admin 时必须为空)。");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("最近一次更新时间(UTC),从未更新时为 null。");
+
+ b.Property("UpdatedBy")
+ .HasColumnType("bigint")
+ .HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TenantId")
+ .HasFilter("\"Portal\" = 1");
+
+ b.HasIndex("RoleId", "PermissionId")
+ .IsUnique()
+ .HasFilter("\"Portal\" = 0");
+
+ b.HasIndex("TenantId", "RoleId", "PermissionId")
+ .IsUnique()
+ .HasFilter("\"Portal\" = 1");
+
+ b.ToTable("role_permissions", null, t =>
+ {
+ t.HasComment("角色-权限关系。");
+
+ t.HasCheckConstraint("CK_role_permissions_Portal_Tenant", "(\"Portal\" = 0 AND \"TenantId\" IS NULL) OR (\"Portal\" = 1 AND \"TenantId\" IS NOT NULL)");
+ });
+ });
+
+ modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.RoleTemplate", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasComment("实体唯一标识。");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("创建时间(UTC)。");
+
+ b.Property("CreatedBy")
+ .HasColumnType("bigint")
+ .HasComment("创建人用户标识,匿名或系统操作时为 null。");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("软删除时间(UTC),未删除时为 null。");
+
+ b.Property("DeletedBy")
+ .HasColumnType("bigint")
+ .HasComment("删除人用户标识(软删除),未删除时为 null。");
+
+ b.Property("Description")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)")
+ .HasComment("模板描述。");
+
+ b.Property("IsActive")
+ .HasColumnType("boolean")
+ .HasComment("是否启用。");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)")
+ .HasComment("模板名称。");
+
+ b.Property("TemplateCode")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasComment("模板编码(唯一)。");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("最近一次更新时间(UTC),从未更新时为 null。");
+
+ b.Property("UpdatedBy")
+ .HasColumnType("bigint")
+ .HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TemplateCode")
+ .IsUnique();
+
+ b.ToTable("role_templates", null, t =>
+ {
+ t.HasComment("角色模板定义(平台级)。");
+ });
+ });
+
+ modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.RoleTemplatePermission", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasComment("实体唯一标识。");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("创建时间(UTC)。");
+
+ b.Property("CreatedBy")
+ .HasColumnType("bigint")
+ .HasComment("创建人用户标识,匿名或系统操作时为 null。");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("软删除时间(UTC),未删除时为 null。");
+
+ b.Property("DeletedBy")
+ .HasColumnType("bigint")
+ .HasComment("删除人用户标识(软删除),未删除时为 null。");
+
+ b.Property("PermissionCode")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)")
+ .HasComment("权限编码。");
+
+ b.Property("RoleTemplateId")
+ .HasColumnType("bigint")
+ .HasComment("模板 ID。");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("最近一次更新时间(UTC),从未更新时为 null。");
+
+ b.Property("UpdatedBy")
+ .HasColumnType("bigint")
+ .HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RoleTemplateId", "PermissionCode")
+ .IsUnique();
+
+ b.ToTable("role_template_permissions", null, t =>
+ {
+ t.HasComment("角色模板-权限关系(平台级)。");
+ });
+ });
+
+ modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.UserRole", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasComment("实体唯一标识。");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("创建时间(UTC)。");
+
+ b.Property("CreatedBy")
+ .HasColumnType("bigint")
+ .HasComment("创建人用户标识,匿名或系统操作时为 null。");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("软删除时间(UTC),未删除时为 null。");
+
+ b.Property("DeletedBy")
+ .HasColumnType("bigint")
+ .HasComment("删除人用户标识(软删除),未删除时为 null。");
+
+ b.Property("Portal")
+ .HasColumnType("integer")
+ .HasComment("关系所属 Portal。");
+
+ b.Property("RoleId")
+ .HasColumnType("bigint")
+ .HasComment("角色 ID。");
+
+ b.Property("TenantId")
+ .HasColumnType("bigint")
+ .HasComment("所属租户 ID(Portal=Tenant 时必填;Portal=Admin 时必须为空)。");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasComment("最近一次更新时间(UTC),从未更新时为 null。");
+
+ b.Property("UpdatedBy")
+ .HasColumnType("bigint")
+ .HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
+
+ b.Property("UserId")
+ .HasColumnType("bigint")
+ .HasComment("用户 ID。");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TenantId")
+ .HasFilter("\"Portal\" = 1");
+
+ b.HasIndex("UserId", "RoleId")
+ .IsUnique()
+ .HasFilter("\"Portal\" = 0");
+
+ b.HasIndex("TenantId", "UserId", "RoleId")
+ .IsUnique()
+ .HasFilter("\"Portal\" = 1");
+
+ b.ToTable("user_roles", null, t =>
+ {
+ t.HasComment("用户-角色关系。");
+
+ t.HasCheckConstraint("CK_user_roles_Portal_Tenant", "(\"Portal\" = 0 AND \"TenantId\" IS NULL) OR (\"Portal\" = 1 AND \"TenantId\" IS NOT NULL)");
+ });
+ });
+
+ modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.OutboxMessage", b =>
+ {
+ b.HasOne("MassTransit.EntityFrameworkCoreIntegration.OutboxState", null)
+ .WithMany()
+ .HasForeignKey("OutboxId");
+
+ b.HasOne("MassTransit.EntityFrameworkCoreIntegration.InboxState", null)
+ .WithMany()
+ .HasForeignKey("InboxMessageId", "InboxConsumerId")
+ .HasPrincipalKey("MessageId", "ConsumerId");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/IdentityDb/20260129111306_PortalizeIdentityRbac.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/IdentityDb/20260129111306_PortalizeIdentityRbac.cs
new file mode 100644
index 0000000..7bfc593
--- /dev/null
+++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/IdentityDb/20260129111306_PortalizeIdentityRbac.cs
@@ -0,0 +1,713 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace TakeoutSaaS.Infrastructure.Migrations.IdentityDb
+{
+ ///
+ public partial class PortalizeIdentityRbac : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropIndex(
+ name: "IX_user_roles_TenantId",
+ table: "user_roles");
+
+ migrationBuilder.DropIndex(
+ name: "IX_user_roles_TenantId_UserId_RoleId",
+ table: "user_roles");
+
+ migrationBuilder.DropIndex(
+ name: "IX_roles_TenantId",
+ table: "roles");
+
+ migrationBuilder.DropIndex(
+ name: "IX_roles_TenantId_Code",
+ table: "roles");
+
+ migrationBuilder.DropIndex(
+ name: "IX_role_permissions_TenantId",
+ table: "role_permissions");
+
+ migrationBuilder.DropIndex(
+ name: "IX_role_permissions_TenantId_RoleId_PermissionId",
+ table: "role_permissions");
+
+ migrationBuilder.DropIndex(
+ name: "IX_permissions_TenantId",
+ table: "permissions");
+
+ migrationBuilder.DropIndex(
+ name: "IX_permissions_TenantId_Code",
+ table: "permissions");
+
+ migrationBuilder.DropIndex(
+ name: "IX_permissions_TenantId_ParentId_SortOrder",
+ table: "permissions");
+
+ migrationBuilder.DropIndex(
+ name: "IX_menu_definitions_TenantId_ParentId_SortOrder",
+ table: "menu_definitions");
+
+ migrationBuilder.DropIndex(
+ name: "IX_identity_users_TenantId",
+ table: "identity_users");
+
+ migrationBuilder.DropIndex(
+ name: "IX_identity_users_TenantId_Account",
+ table: "identity_users");
+
+ migrationBuilder.DropIndex(
+ name: "IX_identity_users_TenantId_Email",
+ table: "identity_users");
+
+ migrationBuilder.DropIndex(
+ name: "IX_identity_users_TenantId_Phone",
+ table: "identity_users");
+
+ migrationBuilder.DropColumn(
+ name: "TenantId",
+ table: "permissions");
+
+ migrationBuilder.DropColumn(
+ name: "TenantId",
+ table: "menu_definitions");
+
+ migrationBuilder.AlterTable(
+ name: "menu_definitions",
+ comment: "后台菜单定义(按 Portal 区分 Admin/Tenant 两套菜单树)。",
+ oldComment: "管理端菜单定义。");
+
+ migrationBuilder.AlterTable(
+ name: "identity_users",
+ comment: "后台账户实体(按 Portal 区分平台管理员与租户后台账号)。",
+ oldComment: "管理后台账户实体(平台管理员、租户管理员或商户员工)。");
+
+ migrationBuilder.AlterColumn(
+ name: "TenantId",
+ table: "user_roles",
+ type: "bigint",
+ nullable: true,
+ comment: "所属租户 ID(Portal=Tenant 时必填;Portal=Admin 时必须为空)。",
+ oldClrType: typeof(long),
+ oldType: "bigint",
+ oldComment: "所属租户 ID。");
+
+ migrationBuilder.AddColumn(
+ name: "Portal",
+ table: "user_roles",
+ type: "integer",
+ nullable: false,
+ defaultValue: 0,
+ comment: "关系所属 Portal。");
+
+ migrationBuilder.AlterColumn(
+ name: "TenantId",
+ table: "roles",
+ type: "bigint",
+ nullable: true,
+ comment: "所属租户 ID(Portal=Tenant 时必填;Portal=Admin 时必须为空)。",
+ oldClrType: typeof(long),
+ oldType: "bigint",
+ oldComment: "所属租户 ID。");
+
+ migrationBuilder.AddColumn(
+ name: "Portal",
+ table: "roles",
+ type: "integer",
+ nullable: false,
+ defaultValue: 0,
+ comment: "角色所属 Portal。");
+
+ migrationBuilder.AlterColumn(
+ name: "TenantId",
+ table: "role_permissions",
+ type: "bigint",
+ nullable: true,
+ comment: "所属租户 ID(Portal=Tenant 时必填;Portal=Admin 时必须为空)。",
+ oldClrType: typeof(long),
+ oldType: "bigint",
+ oldComment: "所属租户 ID。");
+
+ migrationBuilder.AddColumn(
+ name: "Portal",
+ table: "role_permissions",
+ type: "integer",
+ nullable: false,
+ defaultValue: 0,
+ comment: "关系所属 Portal。");
+
+ migrationBuilder.AlterColumn(
+ name: "Code",
+ table: "permissions",
+ type: "character varying(128)",
+ maxLength: 128,
+ nullable: false,
+ comment: "权限编码(全局唯一)。",
+ oldClrType: typeof(string),
+ oldType: "character varying(128)",
+ oldMaxLength: 128,
+ oldComment: "权限编码(租户内唯一)。");
+
+ migrationBuilder.AddColumn(
+ name: "Portal",
+ table: "permissions",
+ type: "integer",
+ nullable: false,
+ defaultValue: 0,
+ comment: "权限所属 Portal。");
+
+ migrationBuilder.AddColumn(
+ name: "Portal",
+ table: "menu_definitions",
+ type: "integer",
+ nullable: false,
+ defaultValue: 0,
+ comment: "菜单所属 Portal。");
+
+ migrationBuilder.AlterColumn(
+ name: "TenantId",
+ table: "identity_users",
+ type: "bigint",
+ nullable: true,
+ comment: "所属租户 ID(Portal=Tenant 时必填;Portal=Admin 时必须为空)。",
+ oldClrType: typeof(long),
+ oldType: "bigint",
+ oldComment: "所属租户 ID。");
+
+ migrationBuilder.AddColumn(
+ name: "Portal",
+ table: "identity_users",
+ type: "integer",
+ nullable: false,
+ defaultValue: 0,
+ comment: "账号所属 Portal。");
+
+ migrationBuilder.CreateTable(
+ name: "InboxState",
+ columns: table => new
+ {
+ Id = table.Column(type: "bigint", nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
+ MessageId = table.Column(type: "uuid", nullable: false),
+ ConsumerId = table.Column(type: "uuid", nullable: false),
+ LockId = table.Column(type: "uuid", nullable: false),
+ RowVersion = table.Column(type: "bytea", nullable: true),
+ Received = table.Column(type: "timestamp with time zone", nullable: false),
+ ReceiveCount = table.Column(type: "integer", nullable: false),
+ ExpirationTime = table.Column(type: "timestamp with time zone", nullable: true),
+ Consumed = table.Column(type: "timestamp with time zone", nullable: true),
+ Delivered = table.Column(type: "timestamp with time zone", nullable: true),
+ LastSequenceNumber = table.Column(type: "bigint", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_InboxState", x => x.Id);
+ table.UniqueConstraint("AK_InboxState_MessageId_ConsumerId", x => new { x.MessageId, x.ConsumerId });
+ });
+
+ migrationBuilder.Sql(
+ """
+ -- 1. 角色 Portal 回填:除 PlatformAdmin 外均视为租户端角色
+ UPDATE public.roles SET "Portal" = 1;
+ UPDATE public.roles
+ SET "Portal" = 0,
+ "TenantId" = NULL
+ WHERE "Code" = 'PlatformAdmin';
+
+ -- 2. 用户 Portal 回填:拥有 PlatformAdmin 角色的账号视为平台管理员
+ UPDATE public.identity_users SET "Portal" = 1;
+ UPDATE public.identity_users u
+ SET "Portal" = 0,
+ "TenantId" = NULL
+ WHERE EXISTS (
+ SELECT 1
+ FROM public.user_roles ur
+ JOIN public.roles r ON r."Id" = ur."RoleId"
+ WHERE ur."UserId" = u."Id"
+ AND r."Portal" = 0
+ );
+
+ -- 3. 关系表 Portal 回填:按关联角色 Portal 写入,并对平台侧关系清空 TenantId
+ UPDATE public.user_roles ur
+ SET "Portal" = r."Portal"
+ FROM public.roles r
+ WHERE r."Id" = ur."RoleId";
+
+ UPDATE public.user_roles
+ SET "TenantId" = NULL
+ WHERE "Portal" = 0;
+
+ UPDATE public.role_permissions rp
+ SET "Portal" = r."Portal"
+ FROM public.roles r
+ WHERE r."Id" = rp."RoleId";
+
+ UPDATE public.role_permissions
+ SET "TenantId" = NULL
+ WHERE "Portal" = 0;
+
+ -- 4. 菜单与权限默认归属 Admin Portal(Tenant Portal 后续由 TenantApi 单独初始化)
+ UPDATE public.menu_definitions SET "Portal" = 0;
+ UPDATE public.permissions SET "Portal" = 0;
+ """);
+
+ migrationBuilder.CreateIndex(
+ name: "IX_user_roles_TenantId",
+ table: "user_roles",
+ column: "TenantId",
+ filter: "\"Portal\" = 1");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_user_roles_TenantId_UserId_RoleId",
+ table: "user_roles",
+ columns: new[] { "TenantId", "UserId", "RoleId" },
+ unique: true,
+ filter: "\"Portal\" = 1");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_user_roles_UserId_RoleId",
+ table: "user_roles",
+ columns: new[] { "UserId", "RoleId" },
+ unique: true,
+ filter: "\"Portal\" = 0");
+
+ migrationBuilder.AddCheckConstraint(
+ name: "CK_user_roles_Portal_Tenant",
+ table: "user_roles",
+ sql: "(\"Portal\" = 0 AND \"TenantId\" IS NULL) OR (\"Portal\" = 1 AND \"TenantId\" IS NOT NULL)");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_roles_Code",
+ table: "roles",
+ column: "Code",
+ unique: true,
+ filter: "\"Portal\" = 0");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_roles_TenantId",
+ table: "roles",
+ column: "TenantId",
+ filter: "\"Portal\" = 1");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_roles_TenantId_Code",
+ table: "roles",
+ columns: new[] { "TenantId", "Code" },
+ unique: true,
+ filter: "\"Portal\" = 1");
+
+ migrationBuilder.AddCheckConstraint(
+ name: "CK_roles_Portal_Tenant",
+ table: "roles",
+ sql: "(\"Portal\" = 0 AND \"TenantId\" IS NULL) OR (\"Portal\" = 1 AND \"TenantId\" IS NOT NULL)");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_role_permissions_RoleId_PermissionId",
+ table: "role_permissions",
+ columns: new[] { "RoleId", "PermissionId" },
+ unique: true,
+ filter: "\"Portal\" = 0");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_role_permissions_TenantId",
+ table: "role_permissions",
+ column: "TenantId",
+ filter: "\"Portal\" = 1");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_role_permissions_TenantId_RoleId_PermissionId",
+ table: "role_permissions",
+ columns: new[] { "TenantId", "RoleId", "PermissionId" },
+ unique: true,
+ filter: "\"Portal\" = 1");
+
+ migrationBuilder.AddCheckConstraint(
+ name: "CK_role_permissions_Portal_Tenant",
+ table: "role_permissions",
+ sql: "(\"Portal\" = 0 AND \"TenantId\" IS NULL) OR (\"Portal\" = 1 AND \"TenantId\" IS NOT NULL)");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_permissions_Code",
+ table: "permissions",
+ column: "Code",
+ unique: true);
+
+ migrationBuilder.CreateIndex(
+ name: "IX_permissions_Portal_ParentId_SortOrder",
+ table: "permissions",
+ columns: new[] { "Portal", "ParentId", "SortOrder" });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_menu_definitions_Portal_ParentId_SortOrder",
+ table: "menu_definitions",
+ columns: new[] { "Portal", "ParentId", "SortOrder" });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_identity_users_Account",
+ table: "identity_users",
+ column: "Account",
+ unique: true,
+ filter: "\"Portal\" = 0");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_identity_users_Email",
+ table: "identity_users",
+ column: "Email",
+ unique: true,
+ filter: "\"Portal\" = 0 AND \"Email\" IS NOT NULL");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_identity_users_Phone",
+ table: "identity_users",
+ column: "Phone",
+ unique: true,
+ filter: "\"Portal\" = 0 AND \"Phone\" IS NOT NULL");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_identity_users_TenantId",
+ table: "identity_users",
+ column: "TenantId",
+ filter: "\"Portal\" = 1");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_identity_users_TenantId_Account",
+ table: "identity_users",
+ columns: new[] { "TenantId", "Account" },
+ unique: true,
+ filter: "\"Portal\" = 1");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_identity_users_TenantId_Email",
+ table: "identity_users",
+ columns: new[] { "TenantId", "Email" },
+ unique: true,
+ filter: "\"Portal\" = 1 AND \"Email\" IS NOT NULL");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_identity_users_TenantId_Phone",
+ table: "identity_users",
+ columns: new[] { "TenantId", "Phone" },
+ unique: true,
+ filter: "\"Portal\" = 1 AND \"Phone\" IS NOT NULL");
+
+ migrationBuilder.AddCheckConstraint(
+ name: "CK_identity_users_Portal_Tenant",
+ table: "identity_users",
+ sql: "(\"Portal\" = 0 AND \"TenantId\" IS NULL) OR (\"Portal\" = 1 AND \"TenantId\" IS NOT NULL)");
+
+ migrationBuilder.AddForeignKey(
+ name: "FK_OutboxMessage_InboxState_InboxMessageId_InboxConsumerId",
+ table: "OutboxMessage",
+ columns: new[] { "InboxMessageId", "InboxConsumerId" },
+ principalTable: "InboxState",
+ principalColumns: new[] { "MessageId", "ConsumerId" });
+
+ migrationBuilder.AddForeignKey(
+ name: "FK_OutboxMessage_OutboxState_OutboxId",
+ table: "OutboxMessage",
+ column: "OutboxId",
+ principalTable: "OutboxState",
+ principalColumn: "OutboxId");
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropForeignKey(
+ name: "FK_OutboxMessage_InboxState_InboxMessageId_InboxConsumerId",
+ table: "OutboxMessage");
+
+ migrationBuilder.DropForeignKey(
+ name: "FK_OutboxMessage_OutboxState_OutboxId",
+ table: "OutboxMessage");
+
+ migrationBuilder.DropTable(
+ name: "InboxState");
+
+ migrationBuilder.DropIndex(
+ name: "IX_user_roles_TenantId",
+ table: "user_roles");
+
+ migrationBuilder.DropIndex(
+ name: "IX_user_roles_TenantId_UserId_RoleId",
+ table: "user_roles");
+
+ migrationBuilder.DropIndex(
+ name: "IX_user_roles_UserId_RoleId",
+ table: "user_roles");
+
+ migrationBuilder.DropCheckConstraint(
+ name: "CK_user_roles_Portal_Tenant",
+ table: "user_roles");
+
+ migrationBuilder.DropIndex(
+ name: "IX_roles_Code",
+ table: "roles");
+
+ migrationBuilder.DropIndex(
+ name: "IX_roles_TenantId",
+ table: "roles");
+
+ migrationBuilder.DropIndex(
+ name: "IX_roles_TenantId_Code",
+ table: "roles");
+
+ migrationBuilder.DropCheckConstraint(
+ name: "CK_roles_Portal_Tenant",
+ table: "roles");
+
+ migrationBuilder.DropIndex(
+ name: "IX_role_permissions_RoleId_PermissionId",
+ table: "role_permissions");
+
+ migrationBuilder.DropIndex(
+ name: "IX_role_permissions_TenantId",
+ table: "role_permissions");
+
+ migrationBuilder.DropIndex(
+ name: "IX_role_permissions_TenantId_RoleId_PermissionId",
+ table: "role_permissions");
+
+ migrationBuilder.DropCheckConstraint(
+ name: "CK_role_permissions_Portal_Tenant",
+ table: "role_permissions");
+
+ migrationBuilder.DropIndex(
+ name: "IX_permissions_Code",
+ table: "permissions");
+
+ migrationBuilder.DropIndex(
+ name: "IX_permissions_Portal_ParentId_SortOrder",
+ table: "permissions");
+
+ migrationBuilder.DropIndex(
+ name: "IX_menu_definitions_Portal_ParentId_SortOrder",
+ table: "menu_definitions");
+
+ migrationBuilder.DropIndex(
+ name: "IX_identity_users_Account",
+ table: "identity_users");
+
+ migrationBuilder.DropIndex(
+ name: "IX_identity_users_Email",
+ table: "identity_users");
+
+ migrationBuilder.DropIndex(
+ name: "IX_identity_users_Phone",
+ table: "identity_users");
+
+ migrationBuilder.DropIndex(
+ name: "IX_identity_users_TenantId",
+ table: "identity_users");
+
+ migrationBuilder.DropIndex(
+ name: "IX_identity_users_TenantId_Account",
+ table: "identity_users");
+
+ migrationBuilder.DropIndex(
+ name: "IX_identity_users_TenantId_Email",
+ table: "identity_users");
+
+ migrationBuilder.DropIndex(
+ name: "IX_identity_users_TenantId_Phone",
+ table: "identity_users");
+
+ migrationBuilder.DropCheckConstraint(
+ name: "CK_identity_users_Portal_Tenant",
+ table: "identity_users");
+
+ migrationBuilder.DropColumn(
+ name: "Portal",
+ table: "user_roles");
+
+ migrationBuilder.DropColumn(
+ name: "Portal",
+ table: "roles");
+
+ migrationBuilder.DropColumn(
+ name: "Portal",
+ table: "role_permissions");
+
+ migrationBuilder.DropColumn(
+ name: "Portal",
+ table: "permissions");
+
+ migrationBuilder.DropColumn(
+ name: "Portal",
+ table: "menu_definitions");
+
+ migrationBuilder.DropColumn(
+ name: "Portal",
+ table: "identity_users");
+
+ migrationBuilder.AlterTable(
+ name: "menu_definitions",
+ comment: "管理端菜单定义。",
+ oldComment: "后台菜单定义(按 Portal 区分 Admin/Tenant 两套菜单树)。");
+
+ migrationBuilder.AlterTable(
+ name: "identity_users",
+ comment: "管理后台账户实体(平台管理员、租户管理员或商户员工)。",
+ oldComment: "后台账户实体(按 Portal 区分平台管理员与租户后台账号)。");
+
+ migrationBuilder.AlterColumn(
+ name: "TenantId",
+ table: "user_roles",
+ type: "bigint",
+ nullable: false,
+ defaultValue: 0L,
+ comment: "所属租户 ID。",
+ oldClrType: typeof(long),
+ oldType: "bigint",
+ oldNullable: true,
+ oldComment: "所属租户 ID(Portal=Tenant 时必填;Portal=Admin 时必须为空)。");
+
+ migrationBuilder.AlterColumn(
+ name: "TenantId",
+ table: "roles",
+ type: "bigint",
+ nullable: false,
+ defaultValue: 0L,
+ comment: "所属租户 ID。",
+ oldClrType: typeof(long),
+ oldType: "bigint",
+ oldNullable: true,
+ oldComment: "所属租户 ID(Portal=Tenant 时必填;Portal=Admin 时必须为空)。");
+
+ migrationBuilder.AlterColumn(
+ name: "TenantId",
+ table: "role_permissions",
+ type: "bigint",
+ nullable: false,
+ defaultValue: 0L,
+ comment: "所属租户 ID。",
+ oldClrType: typeof(long),
+ oldType: "bigint",
+ oldNullable: true,
+ oldComment: "所属租户 ID(Portal=Tenant 时必填;Portal=Admin 时必须为空)。");
+
+ migrationBuilder.AlterColumn(
+ name: "Code",
+ table: "permissions",
+ type: "character varying(128)",
+ maxLength: 128,
+ nullable: false,
+ comment: "权限编码(租户内唯一)。",
+ oldClrType: typeof(string),
+ oldType: "character varying(128)",
+ oldMaxLength: 128,
+ oldComment: "权限编码(全局唯一)。");
+
+ migrationBuilder.AddColumn(
+ name: "TenantId",
+ table: "permissions",
+ type: "bigint",
+ nullable: false,
+ defaultValue: 0L,
+ comment: "所属租户 ID。");
+
+ migrationBuilder.AddColumn(
+ name: "TenantId",
+ table: "menu_definitions",
+ type: "bigint",
+ nullable: false,
+ defaultValue: 0L,
+ comment: "所属租户 ID。");
+
+ migrationBuilder.AlterColumn(
+ name: "TenantId",
+ table: "identity_users",
+ type: "bigint",
+ nullable: false,
+ defaultValue: 0L,
+ comment: "所属租户 ID。",
+ oldClrType: typeof(long),
+ oldType: "bigint",
+ oldNullable: true,
+ oldComment: "所属租户 ID(Portal=Tenant 时必填;Portal=Admin 时必须为空)。");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_user_roles_TenantId",
+ table: "user_roles",
+ column: "TenantId");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_user_roles_TenantId_UserId_RoleId",
+ table: "user_roles",
+ columns: new[] { "TenantId", "UserId", "RoleId" },
+ unique: true);
+
+ migrationBuilder.CreateIndex(
+ name: "IX_roles_TenantId",
+ table: "roles",
+ column: "TenantId");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_roles_TenantId_Code",
+ table: "roles",
+ columns: new[] { "TenantId", "Code" },
+ unique: true);
+
+ migrationBuilder.CreateIndex(
+ name: "IX_role_permissions_TenantId",
+ table: "role_permissions",
+ column: "TenantId");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_role_permissions_TenantId_RoleId_PermissionId",
+ table: "role_permissions",
+ columns: new[] { "TenantId", "RoleId", "PermissionId" },
+ unique: true);
+
+ migrationBuilder.CreateIndex(
+ name: "IX_permissions_TenantId",
+ table: "permissions",
+ column: "TenantId");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_permissions_TenantId_Code",
+ table: "permissions",
+ columns: new[] { "TenantId", "Code" },
+ unique: true);
+
+ migrationBuilder.CreateIndex(
+ name: "IX_permissions_TenantId_ParentId_SortOrder",
+ table: "permissions",
+ columns: new[] { "TenantId", "ParentId", "SortOrder" });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_menu_definitions_TenantId_ParentId_SortOrder",
+ table: "menu_definitions",
+ columns: new[] { "TenantId", "ParentId", "SortOrder" });
+
+ 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_identity_users_TenantId_Email",
+ table: "identity_users",
+ columns: new[] { "TenantId", "Email" },
+ unique: true,
+ filter: "\"Email\" IS NOT NULL");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_identity_users_TenantId_Phone",
+ table: "identity_users",
+ columns: new[] { "TenantId", "Phone" },
+ unique: true,
+ filter: "\"Phone\" IS NOT NULL");
+ }
+ }
+}
diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/IdentityDb/IdentityDbContextModelSnapshot.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/IdentityDb/IdentityDbContextModelSnapshot.cs
index 07a3cad..66dbca6 100644
--- a/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/IdentityDb/IdentityDbContextModelSnapshot.cs
+++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/IdentityDb/IdentityDbContextModelSnapshot.cs
@@ -17,11 +17,54 @@ namespace TakeoutSaaS.Infrastructure.Migrations.IdentityDb
{
#pragma warning disable 612, 618
modelBuilder
- .HasAnnotation("ProductVersion", "10.0.0")
+ .HasAnnotation("ProductVersion", "10.0.1")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+ modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.InboxState", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Consumed")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("ConsumerId")
+ .HasColumnType("uuid");
+
+ b.Property("Delivered")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("ExpirationTime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("LastSequenceNumber")
+ .HasColumnType("bigint");
+
+ b.Property("LockId")
+ .HasColumnType("uuid");
+
+ b.Property("MessageId")
+ .HasColumnType("uuid");
+
+ b.Property("ReceiveCount")
+ .HasColumnType("integer");
+
+ b.Property("Received")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("RowVersion")
+ .HasColumnType("bytea");
+
+ b.HasKey("Id");
+
+ b.ToTable("InboxState");
+ });
+
modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.OutboxMessage", b =>
{
b.Property("SequenceNumber")
@@ -220,6 +263,10 @@ namespace TakeoutSaaS.Infrastructure.Migrations.IdentityDb
.HasColumnType("character varying(32)")
.HasComment("手机号(租户内唯一)。");
+ b.Property("Portal")
+ .HasColumnType("integer")
+ .HasComment("账号所属 Portal。");
+
b.Property("RowVersion")
.IsConcurrencyToken()
.IsRequired()
@@ -231,9 +278,9 @@ namespace TakeoutSaaS.Infrastructure.Migrations.IdentityDb
.HasColumnType("integer")
.HasComment("账号状态。");
- b.Property("TenantId")
+ b.Property("TenantId")
.HasColumnType("bigint")
- .HasComment("所属租户 ID。");
+ .HasComment("所属租户 ID(Portal=Tenant 时必填;Portal=Admin 时必须为空)。");
b.Property("UpdatedAt")
.HasColumnType("timestamp with time zone")
@@ -245,22 +292,38 @@ namespace TakeoutSaaS.Infrastructure.Migrations.IdentityDb
b.HasKey("Id");
- b.HasIndex("TenantId");
+ b.HasIndex("Account")
+ .IsUnique()
+ .HasFilter("\"Portal\" = 0");
+
+ b.HasIndex("Email")
+ .IsUnique()
+ .HasFilter("\"Portal\" = 0 AND \"Email\" IS NOT NULL");
+
+ b.HasIndex("Phone")
+ .IsUnique()
+ .HasFilter("\"Portal\" = 0 AND \"Phone\" IS NOT NULL");
+
+ b.HasIndex("TenantId")
+ .HasFilter("\"Portal\" = 1");
b.HasIndex("TenantId", "Account")
- .IsUnique();
+ .IsUnique()
+ .HasFilter("\"Portal\" = 1");
b.HasIndex("TenantId", "Email")
.IsUnique()
- .HasFilter("\"Email\" IS NOT NULL");
+ .HasFilter("\"Portal\" = 1 AND \"Email\" IS NOT NULL");
b.HasIndex("TenantId", "Phone")
.IsUnique()
- .HasFilter("\"Phone\" IS NOT NULL");
+ .HasFilter("\"Portal\" = 1 AND \"Phone\" IS NOT NULL");
b.ToTable("identity_users", null, t =>
{
- t.HasComment("管理后台账户实体(平台管理员、租户管理员或商户员工)。");
+ t.HasComment("后台账户实体(按 Portal 区分平台管理员与租户后台账号)。");
+
+ t.HasCheckConstraint("CK_identity_users_Portal_Tenant", "(\"Portal\" = 0 AND \"TenantId\" IS NULL) OR (\"Portal\" = 1 AND \"TenantId\" IS NOT NULL)");
});
});
@@ -345,6 +408,10 @@ namespace TakeoutSaaS.Infrastructure.Migrations.IdentityDb
.HasColumnType("character varying(256)")
.HasComment("路由路径。");
+ b.Property("Portal")
+ .HasColumnType("integer")
+ .HasComment("菜单所属 Portal。");
+
b.Property("RequiredPermissions")
.IsRequired()
.HasMaxLength(1024)
@@ -355,10 +422,6 @@ namespace TakeoutSaaS.Infrastructure.Migrations.IdentityDb
.HasColumnType("integer")
.HasComment("排序。");
- b.Property("TenantId")
- .HasColumnType("bigint")
- .HasComment("所属租户 ID。");
-
b.Property("Title")
.IsRequired()
.HasMaxLength(128)
@@ -375,11 +438,11 @@ namespace TakeoutSaaS.Infrastructure.Migrations.IdentityDb
b.HasKey("Id");
- b.HasIndex("TenantId", "ParentId", "SortOrder");
+ b.HasIndex("Portal", "ParentId", "SortOrder");
b.ToTable("menu_definitions", null, t =>
{
- t.HasComment("管理端菜单定义。");
+ t.HasComment("后台菜单定义(按 Portal 区分 Admin/Tenant 两套菜单树)。");
});
});
@@ -467,7 +530,7 @@ namespace TakeoutSaaS.Infrastructure.Migrations.IdentityDb
.IsRequired()
.HasMaxLength(128)
.HasColumnType("character varying(128)")
- .HasComment("权限编码(租户内唯一)。");
+ .HasComment("权限编码(全局唯一)。");
b.Property("CreatedAt")
.HasColumnType("timestamp with time zone")
@@ -500,14 +563,14 @@ namespace TakeoutSaaS.Infrastructure.Migrations.IdentityDb
.HasColumnType("bigint")
.HasComment("父级权限 ID,根节点为 0。");
+ b.Property("Portal")
+ .HasColumnType("integer")
+ .HasComment("权限所属 Portal。");
+
b.Property("SortOrder")
.HasColumnType("integer")
.HasComment("排序值,值越小越靠前。");
- b.Property("TenantId")
- .HasColumnType("bigint")
- .HasComment("所属租户 ID。");
-
b.Property("Type")
.IsRequired()
.HasMaxLength(16)
@@ -524,12 +587,10 @@ namespace TakeoutSaaS.Infrastructure.Migrations.IdentityDb
b.HasKey("Id");
- b.HasIndex("TenantId");
-
- b.HasIndex("TenantId", "Code")
+ b.HasIndex("Code")
.IsUnique();
- b.HasIndex("TenantId", "ParentId", "SortOrder");
+ b.HasIndex("Portal", "ParentId", "SortOrder");
b.ToTable("permissions", null, t =>
{
@@ -579,9 +640,13 @@ namespace TakeoutSaaS.Infrastructure.Migrations.IdentityDb
.HasColumnType("character varying(64)")
.HasComment("角色名称。");
- b.Property("TenantId")
+ b.Property("Portal")
+ .HasColumnType("integer")
+ .HasComment("角色所属 Portal。");
+
+ b.Property("TenantId")
.HasColumnType("bigint")
- .HasComment("所属租户 ID。");
+ .HasComment("所属租户 ID(Portal=Tenant 时必填;Portal=Admin 时必须为空)。");
b.Property("UpdatedAt")
.HasColumnType("timestamp with time zone")
@@ -593,14 +658,22 @@ namespace TakeoutSaaS.Infrastructure.Migrations.IdentityDb
b.HasKey("Id");
- b.HasIndex("TenantId");
+ b.HasIndex("Code")
+ .IsUnique()
+ .HasFilter("\"Portal\" = 0");
+
+ b.HasIndex("TenantId")
+ .HasFilter("\"Portal\" = 1");
b.HasIndex("TenantId", "Code")
- .IsUnique();
+ .IsUnique()
+ .HasFilter("\"Portal\" = 1");
b.ToTable("roles", null, t =>
{
t.HasComment("角色定义。");
+
+ t.HasCheckConstraint("CK_roles_Portal_Tenant", "(\"Portal\" = 0 AND \"TenantId\" IS NULL) OR (\"Portal\" = 1 AND \"TenantId\" IS NOT NULL)");
});
});
@@ -633,13 +706,17 @@ namespace TakeoutSaaS.Infrastructure.Migrations.IdentityDb
.HasColumnType("bigint")
.HasComment("权限 ID。");
+ b.Property("Portal")
+ .HasColumnType("integer")
+ .HasComment("关系所属 Portal。");
+
b.Property("RoleId")
.HasColumnType("bigint")
.HasComment("角色 ID。");
- b.Property("TenantId")
+ b.Property("TenantId")
.HasColumnType("bigint")
- .HasComment("所属租户 ID。");
+ .HasComment("所属租户 ID(Portal=Tenant 时必填;Portal=Admin 时必须为空)。");
b.Property("UpdatedAt")
.HasColumnType("timestamp with time zone")
@@ -651,14 +728,22 @@ namespace TakeoutSaaS.Infrastructure.Migrations.IdentityDb
b.HasKey("Id");
- b.HasIndex("TenantId");
+ b.HasIndex("TenantId")
+ .HasFilter("\"Portal\" = 1");
+
+ b.HasIndex("RoleId", "PermissionId")
+ .IsUnique()
+ .HasFilter("\"Portal\" = 0");
b.HasIndex("TenantId", "RoleId", "PermissionId")
- .IsUnique();
+ .IsUnique()
+ .HasFilter("\"Portal\" = 1");
b.ToTable("role_permissions", null, t =>
{
t.HasComment("角色-权限关系。");
+
+ t.HasCheckConstraint("CK_role_permissions_Portal_Tenant", "(\"Portal\" = 0 AND \"TenantId\" IS NULL) OR (\"Portal\" = 1 AND \"TenantId\" IS NOT NULL)");
});
});
@@ -806,13 +891,17 @@ namespace TakeoutSaaS.Infrastructure.Migrations.IdentityDb
.HasColumnType("bigint")
.HasComment("删除人用户标识(软删除),未删除时为 null。");
+ b.Property("Portal")
+ .HasColumnType("integer")
+ .HasComment("关系所属 Portal。");
+
b.Property("RoleId")
.HasColumnType("bigint")
.HasComment("角色 ID。");
- b.Property("TenantId")
+ b.Property("TenantId")
.HasColumnType("bigint")
- .HasComment("所属租户 ID。");
+ .HasComment("所属租户 ID(Portal=Tenant 时必填;Portal=Admin 时必须为空)。");
b.Property("UpdatedAt")
.HasColumnType("timestamp with time zone")
@@ -828,16 +917,36 @@ namespace TakeoutSaaS.Infrastructure.Migrations.IdentityDb
b.HasKey("Id");
- b.HasIndex("TenantId");
+ b.HasIndex("TenantId")
+ .HasFilter("\"Portal\" = 1");
+
+ b.HasIndex("UserId", "RoleId")
+ .IsUnique()
+ .HasFilter("\"Portal\" = 0");
b.HasIndex("TenantId", "UserId", "RoleId")
- .IsUnique();
+ .IsUnique()
+ .HasFilter("\"Portal\" = 1");
b.ToTable("user_roles", null, t =>
{
t.HasComment("用户-角色关系。");
+
+ t.HasCheckConstraint("CK_user_roles_Portal_Tenant", "(\"Portal\" = 0 AND \"TenantId\" IS NULL) OR (\"Portal\" = 1 AND \"TenantId\" IS NOT NULL)");
});
});
+
+ modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.OutboxMessage", b =>
+ {
+ b.HasOne("MassTransit.EntityFrameworkCoreIntegration.OutboxState", null)
+ .WithMany()
+ .HasForeignKey("OutboxId");
+
+ b.HasOne("MassTransit.EntityFrameworkCoreIntegration.InboxState", null)
+ .WithMany()
+ .HasForeignKey("InboxMessageId", "InboxConsumerId")
+ .HasPrincipalKey("MessageId", "ConsumerId");
+ });
#pragma warning restore 612, 618
}
}