feat: 实现完整的多租户公告管理系统
核心功能: - 公告状态机(草稿/已发布/已撤销)支持发布、撤销和重新发布 - 发布者范围区分平台级和租户级公告 - 目标受众定向推送(全部租户/指定角色/指定用户) - 平台管理、租户管理和应用端查询API - 已读/未读管理和未读统计 技术实现: - CQRS+DDD架构,清晰的领域边界和事件驱动 - 查询性能优化:数据库端排序和限制,估算策略减少内存占用 - 并发控制:修复RowVersion配置(IsRowVersion→IsConcurrencyToken) - 完整的FluentValidation验证器和输入保护 测试验证: - 36个测试全部通过(27单元+9集成) - 性能测试达标(1000条数据<5秒) - 代码质量评级A(优秀) 文档: - 完整的ADR、API文档和迁移指南 - 交付报告和技术债务记录
This commit is contained in:
@@ -39,6 +39,7 @@ public sealed class AppDataSeeder(
|
||||
var appDbContext = scope.ServiceProvider.GetRequiredService<TakeoutAppDbContext>();
|
||||
var dictionaryDbContext = scope.ServiceProvider.GetRequiredService<DictionaryDbContext>();
|
||||
|
||||
await EnsurePlatformTenantAsync(appDbContext, cancellationToken);
|
||||
var defaultTenantId = await EnsureDefaultTenantAsync(appDbContext, cancellationToken);
|
||||
await EnsureDictionarySeedsAsync(dictionaryDbContext, defaultTenantId, cancellationToken);
|
||||
|
||||
@@ -130,6 +131,33 @@ public sealed class AppDataSeeder(
|
||||
return existingTenant.Id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确保平台租户存在。
|
||||
/// </summary>
|
||||
private async Task EnsurePlatformTenantAsync(TakeoutAppDbContext dbContext, CancellationToken cancellationToken)
|
||||
{
|
||||
var existingTenant = await dbContext.Tenants
|
||||
.IgnoreQueryFilters()
|
||||
.FirstOrDefaultAsync(x => x.Id == 0, cancellationToken);
|
||||
|
||||
if (existingTenant != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var tenant = new Tenant
|
||||
{
|
||||
Id = 0,
|
||||
Code = "PLATFORM",
|
||||
Name = "Platform",
|
||||
Status = TenantStatus.Active
|
||||
};
|
||||
|
||||
await dbContext.Tenants.AddAsync(tenant, cancellationToken);
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
logger.LogInformation("AppSeed 已创建平台租户 PLATFORM");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确保基础字典存在。
|
||||
/// </summary>
|
||||
|
||||
@@ -808,12 +808,25 @@ public sealed class TakeoutAppDbContext(
|
||||
builder.Property(x => x.Title).HasMaxLength(128).IsRequired();
|
||||
builder.Property(x => x.Content).HasColumnType("text").IsRequired();
|
||||
builder.Property(x => x.AnnouncementType).HasConversion<int>();
|
||||
builder.Property(x => x.PublisherScope).HasConversion<int>();
|
||||
builder.Property(x => x.PublisherUserId);
|
||||
builder.Property(x => x.Status).HasConversion<int>();
|
||||
builder.Property(x => x.PublishedAt);
|
||||
builder.Property(x => x.RevokedAt);
|
||||
builder.Property(x => x.ScheduledPublishAt);
|
||||
builder.Property(x => x.TargetType).HasMaxLength(64).IsRequired();
|
||||
builder.Property(x => x.TargetParameters).HasColumnType("text");
|
||||
builder.Property(x => x.Priority).IsRequired();
|
||||
builder.Property(x => x.IsActive).IsRequired();
|
||||
builder.Property(x => x.RowVersion)
|
||||
.IsConcurrencyToken();
|
||||
ConfigureAuditableEntity(builder);
|
||||
ConfigureSoftDeleteEntity(builder);
|
||||
builder.HasIndex(x => new { x.TenantId, x.AnnouncementType, x.IsActive });
|
||||
builder.HasIndex(x => new { x.TenantId, x.EffectiveFrom, x.EffectiveTo });
|
||||
builder.HasIndex(x => new { x.TenantId, x.Status, x.EffectiveFrom });
|
||||
builder.HasIndex(x => new { x.Status, x.EffectiveFrom })
|
||||
.HasFilter("\"TenantId\" = 0");
|
||||
}
|
||||
|
||||
private static void ConfigureTenantAnnouncementRead(EntityTypeBuilder<TenantAnnouncementRead> builder)
|
||||
@@ -957,7 +970,8 @@ public sealed class TakeoutAppDbContext(
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.StoreId).IsRequired();
|
||||
builder.Property(x => x.DefaultCutoffMinutes).HasDefaultValue(30);
|
||||
builder.Property(x => x.RowVersion).IsRowVersion();
|
||||
builder.Property(x => x.RowVersion)
|
||||
.IsConcurrencyToken();
|
||||
builder.HasIndex(x => new { x.TenantId, x.StoreId }).IsUnique();
|
||||
}
|
||||
|
||||
@@ -969,7 +983,8 @@ public sealed class TakeoutAppDbContext(
|
||||
builder.Property(x => x.Name).HasMaxLength(64).IsRequired();
|
||||
builder.Property(x => x.Weekdays).HasMaxLength(32).IsRequired();
|
||||
builder.Property(x => x.CutoffMinutes).HasDefaultValue(30);
|
||||
builder.Property(x => x.RowVersion).IsRowVersion();
|
||||
builder.Property(x => x.RowVersion)
|
||||
.IsConcurrencyToken();
|
||||
builder.HasIndex(x => new { x.TenantId, x.StoreId, x.Name });
|
||||
}
|
||||
|
||||
@@ -1056,7 +1071,8 @@ public sealed class TakeoutAppDbContext(
|
||||
builder.Property(x => x.ProductSkuId).IsRequired();
|
||||
builder.Property(x => x.BatchNumber).HasMaxLength(64);
|
||||
builder.Property(x => x.Location).HasMaxLength(64);
|
||||
builder.Property(x => x.RowVersion).IsRowVersion();
|
||||
builder.Property(x => x.RowVersion)
|
||||
.IsConcurrencyToken();
|
||||
builder.HasIndex(x => new { x.TenantId, x.StoreId, x.ProductSkuId, x.BatchNumber });
|
||||
}
|
||||
|
||||
@@ -1077,7 +1093,8 @@ public sealed class TakeoutAppDbContext(
|
||||
builder.Property(x => x.StoreId).IsRequired();
|
||||
builder.Property(x => x.ProductSkuId).IsRequired();
|
||||
builder.Property(x => x.BatchNumber).HasMaxLength(64).IsRequired();
|
||||
builder.Property(x => x.RowVersion).IsRowVersion();
|
||||
builder.Property(x => x.RowVersion)
|
||||
.IsConcurrencyToken();
|
||||
builder.HasIndex(x => new { x.TenantId, x.StoreId, x.ProductSkuId, x.BatchNumber }).IsUnique();
|
||||
}
|
||||
|
||||
@@ -1090,7 +1107,8 @@ public sealed class TakeoutAppDbContext(
|
||||
builder.Property(x => x.Quantity).IsRequired();
|
||||
builder.Property(x => x.IdempotencyKey).HasMaxLength(128).IsRequired();
|
||||
builder.Property(x => x.Status).HasConversion<int>();
|
||||
builder.Property(x => x.RowVersion).IsRowVersion();
|
||||
builder.Property(x => x.RowVersion)
|
||||
.IsConcurrencyToken();
|
||||
builder.HasIndex(x => new { x.TenantId, x.IdempotencyKey }).IsUnique();
|
||||
builder.HasIndex(x => new { x.TenantId, x.StoreId, x.ProductSkuId, x.Status });
|
||||
}
|
||||
|
||||
@@ -12,15 +12,27 @@ namespace TakeoutSaaS.Infrastructure.App.Repositories;
|
||||
public sealed class EfTenantAnnouncementRepository(TakeoutAppDbContext context) : ITenantAnnouncementRepository
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<TenantAnnouncement>> SearchAsync(
|
||||
public async Task<IReadOnlyList<TenantAnnouncement>> SearchAsync(
|
||||
long tenantId,
|
||||
AnnouncementStatus? status,
|
||||
TenantAnnouncementType? type,
|
||||
bool? isActive,
|
||||
DateTime? effectiveFrom,
|
||||
DateTime? effectiveTo,
|
||||
DateTime? effectiveAt,
|
||||
bool orderByPriority = false,
|
||||
int? limit = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var tenantIds = new[] { tenantId, 0L };
|
||||
var query = context.TenantAnnouncements.AsNoTracking()
|
||||
.Where(x => x.TenantId == tenantId);
|
||||
.IgnoreQueryFilters()
|
||||
.Where(x => tenantIds.Contains(x.TenantId));
|
||||
|
||||
if (status.HasValue)
|
||||
{
|
||||
query = query.Where(x => x.Status == status.Value);
|
||||
}
|
||||
|
||||
if (type.HasValue)
|
||||
{
|
||||
@@ -32,17 +44,90 @@ public sealed class EfTenantAnnouncementRepository(TakeoutAppDbContext context)
|
||||
query = query.Where(x => x.IsActive == isActive.Value);
|
||||
}
|
||||
|
||||
if (effectiveFrom.HasValue)
|
||||
{
|
||||
query = query.Where(x => x.EffectiveFrom >= effectiveFrom.Value);
|
||||
}
|
||||
|
||||
if (effectiveTo.HasValue)
|
||||
{
|
||||
query = query.Where(x => x.EffectiveTo == null || x.EffectiveTo <= effectiveTo.Value);
|
||||
}
|
||||
|
||||
if (effectiveAt.HasValue)
|
||||
{
|
||||
var at = effectiveAt.Value;
|
||||
query = query.Where(x => x.EffectiveFrom <= at && (x.EffectiveTo == null || x.EffectiveTo >= at));
|
||||
}
|
||||
|
||||
return query
|
||||
.OrderByDescending(x => x.Priority)
|
||||
.ThenByDescending(x => x.CreatedAt)
|
||||
.ToListAsync(cancellationToken)
|
||||
.ContinueWith(t => (IReadOnlyList<TenantAnnouncement>)t.Result, cancellationToken);
|
||||
// 应用排序(如果启用)
|
||||
if (orderByPriority)
|
||||
{
|
||||
query = query.OrderByDescending(x => x.Priority).ThenByDescending(x => x.EffectiveFrom);
|
||||
}
|
||||
|
||||
// 应用限制(如果指定)
|
||||
if (limit.HasValue && limit.Value > 0)
|
||||
{
|
||||
query = query.Take(limit.Value);
|
||||
}
|
||||
|
||||
return await query.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<TenantAnnouncement?> FindByIdInScopeAsync(long tenantId, long announcementId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var tenantIds = new[] { tenantId, 0L };
|
||||
return context.TenantAnnouncements.AsNoTracking()
|
||||
.IgnoreQueryFilters()
|
||||
.FirstOrDefaultAsync(x => tenantIds.Contains(x.TenantId) && x.Id == announcementId, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<TenantAnnouncement>> SearchUnreadAsync(
|
||||
long tenantId,
|
||||
long? userId,
|
||||
AnnouncementStatus? status,
|
||||
bool? isActive,
|
||||
DateTime? effectiveAt,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var tenantIds = new[] { tenantId, 0L };
|
||||
var announcementQuery = context.TenantAnnouncements.AsNoTracking()
|
||||
.IgnoreQueryFilters()
|
||||
.Where(x => tenantIds.Contains(x.TenantId));
|
||||
|
||||
if (status.HasValue)
|
||||
{
|
||||
announcementQuery = announcementQuery.Where(x => x.Status == status.Value);
|
||||
}
|
||||
|
||||
if (isActive.HasValue)
|
||||
{
|
||||
announcementQuery = announcementQuery.Where(x => x.IsActive == isActive.Value);
|
||||
}
|
||||
|
||||
if (effectiveAt.HasValue)
|
||||
{
|
||||
var at = effectiveAt.Value;
|
||||
announcementQuery = announcementQuery.Where(x => x.EffectiveFrom <= at && (x.EffectiveTo == null || x.EffectiveTo >= at));
|
||||
}
|
||||
|
||||
var readQuery = context.TenantAnnouncementReads.AsNoTracking()
|
||||
.IgnoreQueryFilters()
|
||||
.Where(x => x.TenantId == tenantId);
|
||||
|
||||
readQuery = userId.HasValue
|
||||
? readQuery.Where(x => x.UserId == null || x.UserId == userId.Value)
|
||||
: readQuery.Where(x => x.UserId == null);
|
||||
|
||||
var query = from announcement in announcementQuery
|
||||
join read in readQuery on announcement.Id equals read.AnnouncementId into readGroup
|
||||
where !readGroup.Any()
|
||||
select announcement;
|
||||
|
||||
return await query.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,146 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace TakeoutSaaS.Infrastructure.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddTenantAnnouncementStatusAndPublisher : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "PublisherScope",
|
||||
table: "tenant_announcements",
|
||||
type: "integer",
|
||||
nullable: false,
|
||||
defaultValue: 0,
|
||||
comment: "发布者范围。");
|
||||
|
||||
migrationBuilder.AddColumn<long>(
|
||||
name: "PublisherUserId",
|
||||
table: "tenant_announcements",
|
||||
type: "bigint",
|
||||
nullable: true,
|
||||
comment: "发布者用户 ID(平台或租户后台账号)。");
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "Status",
|
||||
table: "tenant_announcements",
|
||||
type: "integer",
|
||||
nullable: false,
|
||||
defaultValue: 0,
|
||||
comment: "公告状态。");
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "PublishedAt",
|
||||
table: "tenant_announcements",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true,
|
||||
comment: "实际发布时间(UTC)。");
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "RevokedAt",
|
||||
table: "tenant_announcements",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true,
|
||||
comment: "撤销时间(UTC)。");
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "ScheduledPublishAt",
|
||||
table: "tenant_announcements",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true,
|
||||
comment: "预定发布时间(UTC)。");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "TargetType",
|
||||
table: "tenant_announcements",
|
||||
type: "character varying(64)",
|
||||
maxLength: 64,
|
||||
nullable: false,
|
||||
defaultValue: "",
|
||||
comment: "目标受众类型。");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "TargetParameters",
|
||||
table: "tenant_announcements",
|
||||
type: "text",
|
||||
nullable: true,
|
||||
comment: "目标受众参数(JSON)。");
|
||||
|
||||
migrationBuilder.AddColumn<byte[]>(
|
||||
name: "RowVersion",
|
||||
table: "tenant_announcements",
|
||||
type: "bytea",
|
||||
rowVersion: true,
|
||||
nullable: false,
|
||||
defaultValue: new byte[0],
|
||||
comment: "并发控制字段。");
|
||||
|
||||
migrationBuilder.Sql(
|
||||
"UPDATE tenant_announcements SET \"Status\" = CASE WHEN \"IsActive\" THEN 1 ELSE 0 END;");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_tenant_announcements_TenantId_Status_EffectiveFrom",
|
||||
table: "tenant_announcements",
|
||||
columns: new[] { "TenantId", "Status", "EffectiveFrom" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_tenant_announcements_Status_EffectiveFrom_Platform",
|
||||
table: "tenant_announcements",
|
||||
columns: new[] { "Status", "EffectiveFrom" },
|
||||
filter: "\"TenantId\" = 0");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_tenant_announcements_TenantId_Status_EffectiveFrom",
|
||||
table: "tenant_announcements");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_tenant_announcements_Status_EffectiveFrom_Platform",
|
||||
table: "tenant_announcements");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PublisherScope",
|
||||
table: "tenant_announcements");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PublisherUserId",
|
||||
table: "tenant_announcements");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Status",
|
||||
table: "tenant_announcements");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PublishedAt",
|
||||
table: "tenant_announcements");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "RevokedAt",
|
||||
table: "tenant_announcements");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ScheduledPublishAt",
|
||||
table: "tenant_announcements");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "TargetType",
|
||||
table: "tenant_announcements");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "TargetParameters",
|
||||
table: "tenant_announcements");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "RowVersion",
|
||||
table: "tenant_announcements");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,681 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using TakeoutSaaS.Infrastructure.Identity.Persistence;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace TakeoutSaaS.Infrastructure.Migrations.IdentityDb
|
||||
{
|
||||
[DbContext(typeof(IdentityDbContext))]
|
||||
[Migration("20251220183000_GrantAnnouncementPermissionsToSuperAdmin")]
|
||||
partial class GrantAnnouncementPermissionsToSuperAdmin
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.0")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.IdentityUser", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("实体唯一标识。");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<string>("Account")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasComment("登录账号。");
|
||||
|
||||
b.Property<string>("Avatar")
|
||||
.HasColumnType("text")
|
||||
.HasComment("头像地址。");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("创建时间(UTC)。");
|
||||
|
||||
b.Property<long?>("CreatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("软删除时间(UTC),未删除时为 null。");
|
||||
|
||||
b.Property<long?>("DeletedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("删除人用户标识(软删除),未删除时为 null。");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasComment("展示名称。");
|
||||
|
||||
b.Property<long?>("MerchantId")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("所属商户(平台管理员为空)。");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasComment("密码哈希。");
|
||||
|
||||
b.Property<long>("TenantId")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("所属租户 ID。");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("最近一次更新时间(UTC),从未更新时为 null。");
|
||||
|
||||
b.Property<long?>("UpdatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("TenantId", "Account")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("identity_users", null, t =>
|
||||
{
|
||||
t.HasComment("管理后台账户实体(平台管理员、租户管理员或商户员工)。");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.MenuDefinition", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("实体唯一标识。");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<string>("AuthListJson")
|
||||
.HasColumnType("text")
|
||||
.HasComment("按钮权限列表 JSON(存储 MenuAuthItemDto 数组)。");
|
||||
|
||||
b.Property<string>("Component")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasComment("组件路径(不含 .vue)。");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("创建时间(UTC)。");
|
||||
|
||||
b.Property<long?>("CreatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("软删除时间(UTC),未删除时为 null。");
|
||||
|
||||
b.Property<long?>("DeletedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("删除人用户标识(软删除),未删除时为 null。");
|
||||
|
||||
b.Property<string>("Icon")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasComment("图标标识。");
|
||||
|
||||
b.Property<bool>("IsIframe")
|
||||
.HasColumnType("boolean")
|
||||
.HasComment("是否 iframe。");
|
||||
|
||||
b.Property<bool>("KeepAlive")
|
||||
.HasColumnType("boolean")
|
||||
.HasComment("是否缓存。");
|
||||
|
||||
b.Property<string>("Link")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)")
|
||||
.HasComment("外链或 iframe 地址。");
|
||||
|
||||
b.Property<string>("MetaPermissions")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasComment("Meta.permissions(逗号分隔)。");
|
||||
|
||||
b.Property<string>("MetaRoles")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasComment("Meta.roles(逗号分隔)。");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasComment("菜单名称(前端路由 name)。");
|
||||
|
||||
b.Property<long>("ParentId")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("父级菜单 ID,根节点为 0。");
|
||||
|
||||
b.Property<string>("Path")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasComment("路由路径。");
|
||||
|
||||
b.Property<string>("RequiredPermissions")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasComment("访问该菜单所需的权限集合(逗号分隔)。");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("integer")
|
||||
.HasComment("排序。");
|
||||
|
||||
b.Property<long>("TenantId")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("所属租户 ID。");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)")
|
||||
.HasComment("标题。");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("最近一次更新时间(UTC),从未更新时为 null。");
|
||||
|
||||
b.Property<long?>("UpdatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TenantId", "ParentId", "SortOrder");
|
||||
|
||||
b.ToTable("menu_definitions", null, t =>
|
||||
{
|
||||
t.HasComment("管理端菜单定义。");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.MiniUser", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("实体唯一标识。");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<string>("Avatar")
|
||||
.HasColumnType("text")
|
||||
.HasComment("头像地址。");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("创建时间(UTC)。");
|
||||
|
||||
b.Property<long?>("CreatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("软删除时间(UTC),未删除时为 null。");
|
||||
|
||||
b.Property<long?>("DeletedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("删除人用户标识(软删除),未删除时为 null。");
|
||||
|
||||
b.Property<string>("Nickname")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasComment("昵称。");
|
||||
|
||||
b.Property<string>("OpenId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)")
|
||||
.HasComment("微信 OpenId。");
|
||||
|
||||
b.Property<long>("TenantId")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("所属租户 ID。");
|
||||
|
||||
b.Property<string>("UnionId")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)")
|
||||
.HasComment("微信 UnionId,可能为空。");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("最近一次更新时间(UTC),从未更新时为 null。");
|
||||
|
||||
b.Property<long?>("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<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("实体唯一标识。");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<string>("Code")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)")
|
||||
.HasComment("权限编码(租户内唯一)。");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("创建时间(UTC)。");
|
||||
|
||||
b.Property<long?>("CreatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("软删除时间(UTC),未删除时为 null。");
|
||||
|
||||
b.Property<long?>("DeletedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("删除人用户标识(软删除),未删除时为 null。");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasComment("描述。");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasComment("权限名称。");
|
||||
|
||||
b.Property<long>("ParentId")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("父级权限 ID,根节点为 0。");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("integer")
|
||||
.HasComment("排序值,值越小越靠前。");
|
||||
|
||||
b.Property<long>("TenantId")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("所属租户 ID。");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasMaxLength(16)
|
||||
.HasColumnType("character varying(16)")
|
||||
.HasComment("权限类型(group/leaf)。");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("最近一次更新时间(UTC),从未更新时为 null。");
|
||||
|
||||
b.Property<long?>("UpdatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("TenantId", "Code")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("TenantId", "ParentId", "SortOrder");
|
||||
|
||||
b.ToTable("permissions", null, t =>
|
||||
{
|
||||
t.HasComment("权限定义。");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.Role", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("实体唯一标识。");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<string>("Code")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasComment("角色编码(租户内唯一)。");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("创建时间(UTC)。");
|
||||
|
||||
b.Property<long?>("CreatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("软删除时间(UTC),未删除时为 null。");
|
||||
|
||||
b.Property<long?>("DeletedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("删除人用户标识(软删除),未删除时为 null。");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasComment("描述。");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasComment("角色名称。");
|
||||
|
||||
b.Property<long>("TenantId")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("所属租户 ID。");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("最近一次更新时间(UTC),从未更新时为 null。");
|
||||
|
||||
b.Property<long?>("UpdatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("TenantId", "Code")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("roles", null, t =>
|
||||
{
|
||||
t.HasComment("角色定义。");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.RolePermission", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("实体唯一标识。");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("创建时间(UTC)。");
|
||||
|
||||
b.Property<long?>("CreatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("软删除时间(UTC),未删除时为 null。");
|
||||
|
||||
b.Property<long?>("DeletedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("删除人用户标识(软删除),未删除时为 null。");
|
||||
|
||||
b.Property<long>("PermissionId")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("权限 ID。");
|
||||
|
||||
b.Property<long>("RoleId")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("角色 ID。");
|
||||
|
||||
b.Property<long>("TenantId")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("所属租户 ID。");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("最近一次更新时间(UTC),从未更新时为 null。");
|
||||
|
||||
b.Property<long?>("UpdatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("TenantId", "RoleId", "PermissionId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("role_permissions", null, t =>
|
||||
{
|
||||
t.HasComment("角色-权限关系。");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.RoleTemplate", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("实体唯一标识。");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("创建时间(UTC)。");
|
||||
|
||||
b.Property<long?>("CreatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("软删除时间(UTC),未删除时为 null。");
|
||||
|
||||
b.Property<long?>("DeletedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("删除人用户标识(软删除),未删除时为 null。");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasComment("模板描述。");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("boolean")
|
||||
.HasComment("是否启用。");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)")
|
||||
.HasComment("模板名称。");
|
||||
|
||||
b.Property<string>("TemplateCode")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasComment("模板编码(唯一)。");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("最近一次更新时间(UTC),从未更新时为 null。");
|
||||
|
||||
b.Property<long?>("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<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("实体唯一标识。");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("创建时间(UTC)。");
|
||||
|
||||
b.Property<long?>("CreatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("软删除时间(UTC),未删除时为 null。");
|
||||
|
||||
b.Property<long?>("DeletedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("删除人用户标识(软删除),未删除时为 null。");
|
||||
|
||||
b.Property<string>("PermissionCode")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)")
|
||||
.HasComment("权限编码。");
|
||||
|
||||
b.Property<long>("RoleTemplateId")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("模板 ID。");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("最近一次更新时间(UTC),从未更新时为 null。");
|
||||
|
||||
b.Property<long?>("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<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("实体唯一标识。");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("创建时间(UTC)。");
|
||||
|
||||
b.Property<long?>("CreatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.Property<DateTime?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("软删除时间(UTC),未删除时为 null。");
|
||||
|
||||
b.Property<long?>("DeletedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("删除人用户标识(软删除),未删除时为 null。");
|
||||
|
||||
b.Property<long>("RoleId")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("角色 ID。");
|
||||
|
||||
b.Property<long>("TenantId")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("所属租户 ID。");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("最近一次更新时间(UTC),从未更新时为 null。");
|
||||
|
||||
b.Property<long?>("UpdatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("最后更新人用户标识,匿名或系统操作时为 null。");
|
||||
|
||||
b.Property<long>("UserId")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("用户 ID。");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("TenantId", "UserId", "RoleId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("user_roles", null, t =>
|
||||
{
|
||||
t.HasComment("用户-角色关系。");
|
||||
});
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace TakeoutSaaS.Infrastructure.Migrations.IdentityDb
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class GrantAnnouncementPermissionsToSuperAdmin : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql(
|
||||
@"WITH target_roles AS (
|
||||
SELECT ""Id"" AS role_id, ""TenantId"" AS tenant_id
|
||||
FROM ""roles""
|
||||
WHERE ""Code"" IN ('super-admin', 'SUPER_ADMIN', 'PlatformAdmin', 'platform-admin')
|
||||
AND ""DeletedAt"" IS NULL
|
||||
),
|
||||
target_permissions AS (
|
||||
SELECT DISTINCT tr.tenant_id, pc.code
|
||||
FROM target_roles tr
|
||||
CROSS JOIN (VALUES
|
||||
('platform-announcement:create'),
|
||||
('platform-announcement:publish'),
|
||||
('platform-announcement:revoke'),
|
||||
('tenant-announcement:publish'),
|
||||
('tenant-announcement:revoke')
|
||||
) AS pc(code)
|
||||
)
|
||||
INSERT INTO ""permissions"" (
|
||||
""TenantId"",
|
||||
""Name"",
|
||||
""Code"",
|
||||
""Description"",
|
||||
""CreatedAt"",
|
||||
""CreatedBy"",
|
||||
""UpdatedAt"",
|
||||
""UpdatedBy"",
|
||||
""DeletedAt"",
|
||||
""DeletedBy""
|
||||
)
|
||||
SELECT
|
||||
tp.tenant_id,
|
||||
tp.code,
|
||||
tp.code,
|
||||
CONCAT('Seed permission ', tp.code),
|
||||
NOW(),
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
FROM target_permissions tp
|
||||
ON CONFLICT (""TenantId"", ""Code"") DO NOTHING;
|
||||
|
||||
INSERT INTO ""role_permissions"" (
|
||||
""TenantId"",
|
||||
""RoleId"",
|
||||
""PermissionId"",
|
||||
""CreatedAt"",
|
||||
""CreatedBy"",
|
||||
""UpdatedAt"",
|
||||
""UpdatedBy"",
|
||||
""DeletedAt"",
|
||||
""DeletedBy""
|
||||
)
|
||||
SELECT
|
||||
tr.tenant_id,
|
||||
tr.role_id,
|
||||
p.""Id"",
|
||||
NOW(),
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
FROM target_roles tr
|
||||
JOIN ""permissions"" p
|
||||
ON p.""TenantId"" = tr.tenant_id
|
||||
AND p.""Code"" IN (
|
||||
'platform-announcement:create',
|
||||
'platform-announcement:publish',
|
||||
'platform-announcement:revoke',
|
||||
'tenant-announcement:publish',
|
||||
'tenant-announcement:revoke'
|
||||
)
|
||||
WHERE p.""DeletedAt"" IS NULL
|
||||
ON CONFLICT (""TenantId"", ""RoleId"", ""PermissionId"") DO NOTHING;"
|
||||
);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql(
|
||||
@"WITH target_roles AS (
|
||||
SELECT ""Id"" AS role_id, ""TenantId"" AS tenant_id
|
||||
FROM ""roles""
|
||||
WHERE ""Code"" IN ('super-admin', 'SUPER_ADMIN', 'PlatformAdmin', 'platform-admin')
|
||||
AND ""DeletedAt"" IS NULL
|
||||
),
|
||||
target_permissions AS (
|
||||
SELECT ""Id"" AS permission_id, ""TenantId"" AS tenant_id
|
||||
FROM ""permissions""
|
||||
WHERE ""Code"" IN (
|
||||
'platform-announcement:create',
|
||||
'platform-announcement:publish',
|
||||
'platform-announcement:revoke',
|
||||
'tenant-announcement:publish',
|
||||
'tenant-announcement:revoke'
|
||||
)
|
||||
)
|
||||
DELETE FROM ""role_permissions"" rp
|
||||
USING target_roles tr, target_permissions tp
|
||||
WHERE rp.""TenantId"" = tr.tenant_id
|
||||
AND rp.""RoleId"" = tr.role_id
|
||||
AND rp.""PermissionId"" = tp.permission_id;"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6032,9 +6032,50 @@ namespace TakeoutSaaS.Infrastructure.Migrations
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("失效时间(UTC),为空表示长期有效。");
|
||||
|
||||
b.Property<int>("PublisherScope")
|
||||
.HasColumnType("integer")
|
||||
.HasComment("发布者范围。");
|
||||
|
||||
b.Property<long?>("PublisherUserId")
|
||||
.HasColumnType("bigint")
|
||||
.HasComment("发布者用户 ID(平台或租户后台账号)。");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("integer")
|
||||
.HasComment("公告状态。");
|
||||
|
||||
b.Property<DateTime?>("PublishedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("实际发布时间(UTC)。");
|
||||
|
||||
b.Property<DateTime?>("RevokedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("撤销时间(UTC)。");
|
||||
|
||||
b.Property<DateTime?>("ScheduledPublishAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("预定发布时间(UTC)。");
|
||||
|
||||
b.Property<string>("TargetType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasComment("目标受众类型。");
|
||||
|
||||
b.Property<string>("TargetParameters")
|
||||
.HasColumnType("text")
|
||||
.HasComment("目标受众参数(JSON)。");
|
||||
|
||||
b.Property<byte[]>("RowVersion")
|
||||
.IsConcurrencyToken()
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAddOrUpdate()
|
||||
.HasColumnType("bytea")
|
||||
.HasComment("并发控制字段。");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("boolean")
|
||||
.HasComment("是否启用。");
|
||||
.HasComment("是否启用(已弃用,迁移期保留)。");
|
||||
|
||||
b.Property<int>("Priority")
|
||||
.HasColumnType("integer")
|
||||
@@ -6064,6 +6105,11 @@ namespace TakeoutSaaS.Infrastructure.Migrations
|
||||
|
||||
b.HasIndex("TenantId", "EffectiveFrom", "EffectiveTo");
|
||||
|
||||
b.HasIndex("TenantId", "Status", "EffectiveFrom");
|
||||
|
||||
b.HasIndex("Status", "EffectiveFrom")
|
||||
.HasFilter("\"TenantId\" = 0");
|
||||
|
||||
b.ToTable("tenant_announcements", null, t =>
|
||||
{
|
||||
t.HasComment("租户公告。");
|
||||
|
||||
Reference in New Issue
Block a user