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 } }