diff --git a/deploy/postgres/create_databases.sql b/deploy/postgres/create_databases.sql index c8732f6..afc0817 100644 --- a/deploy/postgres/create_databases.sql +++ b/deploy/postgres/create_databases.sql @@ -15,6 +15,9 @@ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'hangfire_user') THEN CREATE ROLE hangfire_user LOGIN PASSWORD 'HangFire112233'; END IF; + IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'logs_user') THEN + CREATE ROLE logs_user LOGIN PASSWORD 'Logs112233'; + END IF; END $$; DO $$ @@ -49,6 +52,14 @@ BEGIN END $$; COMMENT ON DATABASE takeout_hangfire_db IS 'Takeout SaaS 调度/Hangfire 数据库'; +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_database WHERE datname = 'takeout_logs_db') THEN + CREATE DATABASE takeout_logs_db OWNER logs_user ENCODING 'UTF8'; + END IF; +END $$; +COMMENT ON DATABASE takeout_logs_db IS 'Takeout SaaS 审计/日志数据库'; + -- Ensure privileges and default schema permissions \connect takeout_app_db GRANT CONNECT, TEMP ON DATABASE takeout_app_db TO app_user; @@ -81,3 +92,11 @@ GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO hangfire_ GRANT USAGE, SELECT, UPDATE ON ALL SEQUENCES IN SCHEMA public TO hangfire_user; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO hangfire_user; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT USAGE, SELECT, UPDATE ON SEQUENCES TO hangfire_user; + +\connect takeout_logs_db +GRANT CONNECT, TEMP ON DATABASE takeout_logs_db TO logs_user; +GRANT USAGE ON SCHEMA public TO logs_user; +GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO logs_user; +GRANT USAGE, SELECT, UPDATE ON ALL SEQUENCES IN SCHEMA public TO logs_user; +ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO logs_user; +ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT USAGE, SELECT, UPDATE ON SEQUENCES TO logs_user; diff --git a/deploy/postgres/migrate_logs_to_logs_db.sql b/deploy/postgres/migrate_logs_to_logs_db.sql new file mode 100644 index 0000000..95892c8 --- /dev/null +++ b/deploy/postgres/migrate_logs_to_logs_db.sql @@ -0,0 +1,89 @@ +-- 日志库迁移脚本(请在 psql 中按步骤执行) + +-- 1. 在日志库创建表结构(takeout_logs_db) +\connect takeout_logs_db + +CREATE TABLE IF NOT EXISTS tenant_audit_logs ( + "Id" bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + "TenantId" bigint NOT NULL, + "Action" integer NOT NULL, + "Title" character varying(128) NOT NULL, + "Description" character varying(1024), + "OperatorId" bigint, + "OperatorName" character varying(64), + "PreviousStatus" integer, + "CurrentStatus" integer, + "CreatedAt" timestamp with time zone NOT NULL, + "UpdatedAt" timestamp with time zone, + "DeletedAt" timestamp with time zone, + "CreatedBy" bigint, + "UpdatedBy" bigint, + "DeletedBy" bigint +); +CREATE INDEX IF NOT EXISTS "IX_tenant_audit_logs_TenantId" ON tenant_audit_logs ("TenantId"); + +CREATE TABLE IF NOT EXISTS merchant_audit_logs ( + "Id" bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + "MerchantId" bigint NOT NULL, + "Action" integer NOT NULL, + "Title" character varying(128) NOT NULL, + "Description" character varying(1024), + "OperatorId" bigint, + "OperatorName" character varying(64), + "CreatedAt" timestamp with time zone NOT NULL, + "UpdatedAt" timestamp with time zone, + "DeletedAt" timestamp with time zone, + "CreatedBy" bigint, + "UpdatedBy" bigint, + "DeletedBy" bigint, + "TenantId" bigint NOT NULL +); +CREATE INDEX IF NOT EXISTS "IX_merchant_audit_logs_TenantId_MerchantId" ON merchant_audit_logs ("TenantId", "MerchantId"); + +CREATE TABLE IF NOT EXISTS operation_logs ( + "Id" bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + "OperationType" character varying(64) NOT NULL, + "TargetType" character varying(64) NOT NULL, + "TargetIds" text, + "OperatorId" character varying(64), + "OperatorName" character varying(128), + "Parameters" text, + "Result" text, + "Success" boolean NOT NULL, + "CreatedAt" timestamp with time zone NOT NULL, + "UpdatedAt" timestamp with time zone, + "DeletedAt" timestamp with time zone, + "CreatedBy" bigint, + "UpdatedBy" bigint, + "DeletedBy" bigint +); +CREATE INDEX IF NOT EXISTS "IX_operation_logs_CreatedAt" ON operation_logs ("CreatedAt"); +CREATE INDEX IF NOT EXISTS "IX_operation_logs_OperationType_CreatedAt" ON operation_logs ("OperationType", "CreatedAt"); + +CREATE TABLE IF NOT EXISTS member_growth_logs ( + "Id" bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + "MemberId" bigint NOT NULL, + "ChangeValue" integer NOT NULL, + "CurrentValue" integer NOT NULL, + "Notes" character varying(256), + "OccurredAt" timestamp with time zone NOT NULL, + "CreatedAt" timestamp with time zone NOT NULL, + "UpdatedAt" timestamp with time zone, + "DeletedAt" timestamp with time zone, + "CreatedBy" bigint, + "UpdatedBy" bigint, + "DeletedBy" bigint, + "TenantId" bigint NOT NULL +); +CREATE INDEX IF NOT EXISTS "IX_member_growth_logs_TenantId_MemberId_OccurredAt" ON member_growth_logs ("TenantId", "MemberId", "OccurredAt"); + +-- 2. 迁移数据(建议使用 pg_dump/pg_restore 或应用侧批量拷贝) +-- 示例:pg_dump -t tenant_audit_logs -t merchant_audit_logs -t operation_logs -t member_growth_logs takeout_app_db > logs_dump.sql +-- psql -d takeout_logs_db -f logs_dump.sql + +-- 3. 在业务库删除旧日志表(takeout_app_db) +\connect takeout_app_db +DROP TABLE IF EXISTS tenant_audit_logs; +DROP TABLE IF EXISTS merchant_audit_logs; +DROP TABLE IF EXISTS operation_logs; +DROP TABLE IF EXISTS member_growth_logs; diff --git a/src/Api/TakeoutSaaS.AdminApi/appsettings.Development.json b/src/Api/TakeoutSaaS.AdminApi/appsettings.Development.json index 55b3266..882ab41 100644 --- a/src/Api/TakeoutSaaS.AdminApi/appsettings.Development.json +++ b/src/Api/TakeoutSaaS.AdminApi/appsettings.Development.json @@ -30,6 +30,15 @@ "CommandTimeoutSeconds": 30, "MaxRetryCount": 3, "MaxRetryDelaySeconds": 5 + }, + "LogsDatabase": { + "Write": "Host=120.53.222.17;Port=5432;Database=takeout_logs_db;Username=logs_user;Password=Logs112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50", + "Reads": [ + "Host=120.53.222.17;Port=5432;Database=takeout_logs_db;Username=logs_user;Password=Logs112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50" + ], + "CommandTimeoutSeconds": 30, + "MaxRetryCount": 3, + "MaxRetryDelaySeconds": 5 } } }, diff --git a/src/Api/TakeoutSaaS.AdminApi/appsettings.Production.json b/src/Api/TakeoutSaaS.AdminApi/appsettings.Production.json index 3955d16..725c21f 100644 --- a/src/Api/TakeoutSaaS.AdminApi/appsettings.Production.json +++ b/src/Api/TakeoutSaaS.AdminApi/appsettings.Production.json @@ -30,6 +30,15 @@ "CommandTimeoutSeconds": 30, "MaxRetryCount": 3, "MaxRetryDelaySeconds": 5 + }, + "LogsDatabase": { + "Write": "Host=120.53.222.17;Port=5432;Database=takeout_logs_db;Username=logs_user;Password=Logs112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50", + "Reads": [ + "Host=120.53.222.17;Port=5432;Database=takeout_logs_db;Username=logs_user;Password=Logs112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50" + ], + "CommandTimeoutSeconds": 30, + "MaxRetryCount": 3, + "MaxRetryDelaySeconds": 5 } } }, diff --git a/src/Api/TakeoutSaaS.MiniApi/appsettings.Development.json b/src/Api/TakeoutSaaS.MiniApi/appsettings.Development.json index 8eb84b7..9f6e1ca 100644 --- a/src/Api/TakeoutSaaS.MiniApi/appsettings.Development.json +++ b/src/Api/TakeoutSaaS.MiniApi/appsettings.Development.json @@ -27,6 +27,15 @@ "CommandTimeoutSeconds": 30, "MaxRetryCount": 3, "MaxRetryDelaySeconds": 5 + }, + "LogsDatabase": { + "Write": "Host=120.53.222.17;Port=5432;Database=takeout_logs_db;Username=logs_user;Password=Logs112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50", + "Reads": [ + "Host=120.53.222.17;Port=5432;Database=takeout_logs_db;Username=logs_user;Password=Logs112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50" + ], + "CommandTimeoutSeconds": 30, + "MaxRetryCount": 3, + "MaxRetryDelaySeconds": 5 } } }, @@ -162,4 +171,4 @@ "Sampling": "ParentBasedAlwaysOn", "UseConsoleExporter": true } -} \ No newline at end of file +} diff --git a/src/Api/TakeoutSaaS.MiniApi/appsettings.Production.json b/src/Api/TakeoutSaaS.MiniApi/appsettings.Production.json index 8eb84b7..9f6e1ca 100644 --- a/src/Api/TakeoutSaaS.MiniApi/appsettings.Production.json +++ b/src/Api/TakeoutSaaS.MiniApi/appsettings.Production.json @@ -27,6 +27,15 @@ "CommandTimeoutSeconds": 30, "MaxRetryCount": 3, "MaxRetryDelaySeconds": 5 + }, + "LogsDatabase": { + "Write": "Host=120.53.222.17;Port=5432;Database=takeout_logs_db;Username=logs_user;Password=Logs112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50", + "Reads": [ + "Host=120.53.222.17;Port=5432;Database=takeout_logs_db;Username=logs_user;Password=Logs112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50" + ], + "CommandTimeoutSeconds": 30, + "MaxRetryCount": 3, + "MaxRetryDelaySeconds": 5 } } }, @@ -162,4 +171,4 @@ "Sampling": "ParentBasedAlwaysOn", "UseConsoleExporter": true } -} \ No newline at end of file +} diff --git a/src/Api/TakeoutSaaS.UserApi/appsettings.Development.json b/src/Api/TakeoutSaaS.UserApi/appsettings.Development.json index 585bef6..17378b5 100644 --- a/src/Api/TakeoutSaaS.UserApi/appsettings.Development.json +++ b/src/Api/TakeoutSaaS.UserApi/appsettings.Development.json @@ -27,6 +27,15 @@ "CommandTimeoutSeconds": 30, "MaxRetryCount": 3, "MaxRetryDelaySeconds": 5 + }, + "LogsDatabase": { + "Write": "Host=120.53.222.17;Port=5432;Database=takeout_logs_db;Username=logs_user;Password=Logs112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50", + "Reads": [ + "Host=120.53.222.17;Port=5432;Database=takeout_logs_db;Username=logs_user;Password=Logs112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50" + ], + "CommandTimeoutSeconds": 30, + "MaxRetryCount": 3, + "MaxRetryDelaySeconds": 5 } } }, @@ -65,4 +74,4 @@ "Sampling": "ParentBasedAlwaysOn", "UseConsoleExporter": true } -} \ No newline at end of file +} diff --git a/src/Api/TakeoutSaaS.UserApi/appsettings.Production.json b/src/Api/TakeoutSaaS.UserApi/appsettings.Production.json index 585bef6..17378b5 100644 --- a/src/Api/TakeoutSaaS.UserApi/appsettings.Production.json +++ b/src/Api/TakeoutSaaS.UserApi/appsettings.Production.json @@ -27,6 +27,15 @@ "CommandTimeoutSeconds": 30, "MaxRetryCount": 3, "MaxRetryDelaySeconds": 5 + }, + "LogsDatabase": { + "Write": "Host=120.53.222.17;Port=5432;Database=takeout_logs_db;Username=logs_user;Password=Logs112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50", + "Reads": [ + "Host=120.53.222.17;Port=5432;Database=takeout_logs_db;Username=logs_user;Password=Logs112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50" + ], + "CommandTimeoutSeconds": 30, + "MaxRetryCount": 3, + "MaxRetryDelaySeconds": 5 } } }, @@ -65,4 +74,4 @@ "Sampling": "ParentBasedAlwaysOn", "UseConsoleExporter": true } -} \ No newline at end of file +} diff --git a/src/Core/TakeoutSaaS.Shared.Abstractions/Constants/DatabaseConstants.cs b/src/Core/TakeoutSaaS.Shared.Abstractions/Constants/DatabaseConstants.cs index 2f4022e..4e9e458 100644 --- a/src/Core/TakeoutSaaS.Shared.Abstractions/Constants/DatabaseConstants.cs +++ b/src/Core/TakeoutSaaS.Shared.Abstractions/Constants/DatabaseConstants.cs @@ -19,4 +19,9 @@ public static class DatabaseConstants /// 字典库(DictionaryDatabase)。 /// public const string DictionaryDataSource = "DictionaryDatabase"; + + /// + /// 日志库(LogsDatabase)。 + /// + public const string LogsDataSource = "LogsDatabase"; } diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Extensions/AppServiceCollectionExtensions.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Extensions/AppServiceCollectionExtensions.cs index fae5267..86d6d8c 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Extensions/AppServiceCollectionExtensions.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Extensions/AppServiceCollectionExtensions.cs @@ -11,6 +11,7 @@ using TakeoutSaaS.Domain.Tenants.Repositories; using TakeoutSaaS.Domain.Tenants.Services; using TakeoutSaaS.Infrastructure.App.Options; using TakeoutSaaS.Infrastructure.App.Persistence; +using TakeoutSaaS.Infrastructure.Logs.Persistence; using TakeoutSaaS.Infrastructure.App.Persistence.Repositories; using TakeoutSaaS.Infrastructure.App.Repositories; using TakeoutSaaS.Infrastructure.App.Services; @@ -34,6 +35,7 @@ public static class AppServiceCollectionExtensions { services.AddDatabaseInfrastructure(configuration); services.AddPostgresDbContext(DatabaseConstants.AppDataSource); + services.AddPostgresDbContext(DatabaseConstants.LogsDataSource); services.AddScoped(); services.AddScoped(); diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDbContext.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDbContext.cs index 39b8931..9d21e02 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDbContext.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDbContext.cs @@ -87,18 +87,10 @@ public sealed class TakeoutAppDbContext( /// public DbSet TenantVerificationProfiles => Set(); /// - /// 租户审计日志。 - /// - public DbSet TenantAuditLogs => Set(); - /// /// 租户审核领取记录。 /// public DbSet TenantReviewClaims => Set(); /// - /// 运营操作日志。 - /// - public DbSet OperationLogs => Set(); - /// /// 配额包定义。 /// public DbSet QuotaPackages => Set(); @@ -123,10 +115,6 @@ public sealed class TakeoutAppDbContext( /// public DbSet MerchantStaff => Set(); /// - /// 商户审计日志。 - /// - public DbSet MerchantAuditLogs => Set(); - /// /// 商户分类。 /// public DbSet MerchantCategories => Set(); @@ -307,10 +295,6 @@ public sealed class TakeoutAppDbContext( /// public DbSet MemberPointLedgers => Set(); /// - /// 成长值日志。 - /// - public DbSet MemberGrowthLogs => Set(); - /// /// 会话记录。 /// public DbSet ChatSessions => Set(); @@ -401,15 +385,12 @@ public sealed class TakeoutAppDbContext( ConfigureTenantAnnouncement(modelBuilder.Entity()); ConfigureTenantAnnouncementRead(modelBuilder.Entity()); ConfigureTenantVerificationProfile(modelBuilder.Entity()); - ConfigureTenantAuditLog(modelBuilder.Entity()); ConfigureTenantReviewClaim(modelBuilder.Entity()); - ConfigureOperationLog(modelBuilder.Entity()); ConfigureQuotaPackage(modelBuilder.Entity()); ConfigureTenantQuotaPackagePurchase(modelBuilder.Entity()); ConfigureMerchantDocument(modelBuilder.Entity()); ConfigureMerchantContract(modelBuilder.Entity()); ConfigureMerchantStaff(modelBuilder.Entity()); - ConfigureMerchantAuditLog(modelBuilder.Entity()); ConfigureMerchantCategory(modelBuilder.Entity()); ConfigureStoreBusinessHour(modelBuilder.Entity()); ConfigureStoreHoliday(modelBuilder.Entity()); @@ -454,7 +435,6 @@ public sealed class TakeoutAppDbContext( ConfigureMemberProfile(modelBuilder.Entity()); ConfigureMemberTier(modelBuilder.Entity()); ConfigureMemberPointLedger(modelBuilder.Entity()); - ConfigureMemberGrowthLog(modelBuilder.Entity()); ConfigureChatSession(modelBuilder.Entity()); ConfigureChatMessage(modelBuilder.Entity()); ConfigureSupportTicket(modelBuilder.Entity()); @@ -512,17 +492,6 @@ public sealed class TakeoutAppDbContext( builder.HasIndex(x => x.TenantId).IsUnique(); } - private static void ConfigureTenantAuditLog(EntityTypeBuilder builder) - { - builder.ToTable("tenant_audit_logs"); - builder.HasKey(x => x.Id); - builder.Property(x => x.TenantId).IsRequired(); - builder.Property(x => x.Title).HasMaxLength(128).IsRequired(); - builder.Property(x => x.Description).HasMaxLength(1024); - builder.Property(x => x.OperatorName).HasMaxLength(64); - builder.HasIndex(x => x.TenantId); - } - private static void ConfigureTenantReviewClaim(EntityTypeBuilder builder) { builder.ToTable("tenant_review_claims"); @@ -537,21 +506,6 @@ public sealed class TakeoutAppDbContext( builder.HasIndex(x => x.TenantId).IsUnique().HasFilter("\"ReleasedAt\" IS NULL AND \"DeletedAt\" IS NULL"); } - private static void ConfigureOperationLog(EntityTypeBuilder builder) - { - builder.ToTable("operation_logs"); - builder.HasKey(x => x.Id); - builder.Property(x => x.OperationType).HasMaxLength(64).IsRequired(); - builder.Property(x => x.TargetType).HasMaxLength(64).IsRequired(); - builder.Property(x => x.TargetIds).HasColumnType("text"); - builder.Property(x => x.OperatorId).HasMaxLength(64); - builder.Property(x => x.OperatorName).HasMaxLength(128); - builder.Property(x => x.Parameters).HasColumnType("text"); - builder.Property(x => x.Result).HasColumnType("text"); - builder.Property(x => x.Success).IsRequired(); - builder.HasIndex(x => new { x.OperationType, x.CreatedAt }); - builder.HasIndex(x => x.CreatedAt); - } private static void ConfigureTenantSubscriptionHistory(EntityTypeBuilder builder) { @@ -885,17 +839,6 @@ public sealed class TakeoutAppDbContext( builder.HasIndex(x => new { x.TenantId, x.MerchantId, x.Phone }); } - private static void ConfigureMerchantAuditLog(EntityTypeBuilder builder) - { - builder.ToTable("merchant_audit_logs"); - builder.HasKey(x => x.Id); - builder.Property(x => x.MerchantId).IsRequired(); - builder.Property(x => x.Title).HasMaxLength(128).IsRequired(); - builder.Property(x => x.Description).HasMaxLength(1024); - builder.Property(x => x.OperatorName).HasMaxLength(64); - builder.HasIndex(x => new { x.TenantId, x.MerchantId }); - } - private static void ConfigureMerchantCategory(EntityTypeBuilder builder) { builder.ToTable("merchant_categories"); @@ -1297,15 +1240,6 @@ public sealed class TakeoutAppDbContext( builder.HasIndex(x => new { x.TenantId, x.MemberId, x.OccurredAt }); } - private static void ConfigureMemberGrowthLog(EntityTypeBuilder builder) - { - builder.ToTable("member_growth_logs"); - builder.HasKey(x => x.Id); - builder.Property(x => x.MemberId).IsRequired(); - builder.Property(x => x.Notes).HasMaxLength(256); - builder.HasIndex(x => new { x.TenantId, x.MemberId, x.OccurredAt }); - } - private static void ConfigureChatSession(EntityTypeBuilder builder) { builder.ToTable("chat_sessions"); diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfMerchantRepository.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfMerchantRepository.cs index 34eeb92..db41cdd 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfMerchantRepository.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfMerchantRepository.cs @@ -3,6 +3,7 @@ using TakeoutSaaS.Domain.Merchants.Entities; using TakeoutSaaS.Domain.Merchants.Enums; using TakeoutSaaS.Domain.Merchants.Repositories; using TakeoutSaaS.Infrastructure.App.Persistence; +using TakeoutSaaS.Infrastructure.Logs.Persistence; namespace TakeoutSaaS.Infrastructure.App.Repositories; @@ -12,7 +13,7 @@ namespace TakeoutSaaS.Infrastructure.App.Repositories; /// /// 初始化仓储。 /// -public sealed class EfMerchantRepository(TakeoutAppDbContext context) : IMerchantRepository +public sealed class EfMerchantRepository(TakeoutAppDbContext context, TakeoutLogsDbContext logsContext) : IMerchantRepository { /// public Task FindByIdAsync(long merchantId, long tenantId, CancellationToken cancellationToken = default) @@ -151,9 +152,13 @@ public sealed class EfMerchantRepository(TakeoutAppDbContext context) : IMerchan } /// - public Task SaveChangesAsync(CancellationToken cancellationToken = default) + public async Task SaveChangesAsync(CancellationToken cancellationToken = default) { - return context.SaveChangesAsync(cancellationToken); + // 1. 保存业务库变更 + await context.SaveChangesAsync(cancellationToken); + + // 2. (空行后) 保存日志库变更 + await logsContext.SaveChangesAsync(cancellationToken); } /// @@ -196,13 +201,13 @@ public sealed class EfMerchantRepository(TakeoutAppDbContext context) : IMerchan /// public Task AddAuditLogAsync(MerchantAuditLog log, CancellationToken cancellationToken = default) { - return context.MerchantAuditLogs.AddAsync(log, cancellationToken).AsTask(); + return logsContext.MerchantAuditLogs.AddAsync(log, cancellationToken).AsTask(); } /// public async Task> GetAuditLogsAsync(long merchantId, long tenantId, CancellationToken cancellationToken = default) { - return await context.MerchantAuditLogs + return await logsContext.MerchantAuditLogs .AsNoTracking() .Where(x => x.TenantId == tenantId && x.MerchantId == merchantId) .OrderByDescending(x => x.CreatedAt) diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfSubscriptionRepository.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfSubscriptionRepository.cs index 0b59a3b..9a1802b 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfSubscriptionRepository.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfSubscriptionRepository.cs @@ -3,13 +3,14 @@ using TakeoutSaaS.Domain.Tenants.Entities; using TakeoutSaaS.Domain.Tenants.Enums; using TakeoutSaaS.Domain.Tenants.Repositories; using TakeoutSaaS.Infrastructure.App.Persistence; +using TakeoutSaaS.Infrastructure.Logs.Persistence; namespace TakeoutSaaS.Infrastructure.App.Repositories; /// /// 订阅管理仓储实现。 /// -public sealed class EfSubscriptionRepository(TakeoutAppDbContext dbContext) : ISubscriptionRepository +public sealed class EfSubscriptionRepository(TakeoutAppDbContext dbContext, TakeoutLogsDbContext logsContext) : ISubscriptionRepository { #region 订阅查询 @@ -393,7 +394,7 @@ public sealed class EfSubscriptionRepository(TakeoutAppDbContext dbContext) : IS /// public Task AddOperationLogAsync(OperationLog log, CancellationToken cancellationToken = default) { - dbContext.Set().Add(log); + logsContext.OperationLogs.Add(log); return Task.CompletedTask; } @@ -402,6 +403,10 @@ public sealed class EfSubscriptionRepository(TakeoutAppDbContext dbContext) : IS /// public async Task SaveChangesAsync(CancellationToken cancellationToken = default) { + // 1. 保存业务库变更 await dbContext.SaveChangesAsync(cancellationToken); + + // 2. (空行后) 保存日志库变更 + await logsContext.SaveChangesAsync(cancellationToken); } } diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfTenantRepository.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfTenantRepository.cs index ce1f485..ecfa185 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfTenantRepository.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfTenantRepository.cs @@ -4,13 +4,14 @@ using TakeoutSaaS.Domain.Tenants.Entities; using TakeoutSaaS.Domain.Tenants.Enums; using TakeoutSaaS.Domain.Tenants.Repositories; using TakeoutSaaS.Infrastructure.App.Persistence; +using TakeoutSaaS.Infrastructure.Logs.Persistence; namespace TakeoutSaaS.Infrastructure.App.Repositories; /// /// 租户聚合的 EF Core 仓储实现。 /// -public sealed class EfTenantRepository(TakeoutAppDbContext context) : ITenantRepository +public sealed class EfTenantRepository(TakeoutAppDbContext context, TakeoutLogsDbContext logsContext) : ITenantRepository { /// public Task FindByIdAsync(long tenantId, CancellationToken cancellationToken = default) @@ -277,16 +278,21 @@ public sealed class EfTenantRepository(TakeoutAppDbContext context) : ITenantRep { try { + // 1. 写入领取记录 await context.TenantReviewClaims.AddAsync(claim, cancellationToken); - await context.TenantAuditLogs.AddAsync(auditLog, cancellationToken); - await context.SaveChangesAsync(cancellationToken); + + // 2. (空行后) 写入审计日志 + await logsContext.TenantAuditLogs.AddAsync(auditLog, cancellationToken); + await logsContext.SaveChangesAsync(cancellationToken); return true; } catch (DbUpdateException ex) when (ex.InnerException is PostgresException pg && pg.SqlState == PostgresErrorCodes.UniqueViolation) { + // 1. 释放实体跟踪避免重复写入 context.Entry(claim).State = EntityState.Detached; - context.Entry(auditLog).State = EntityState.Detached; + + // 2. (空行后) 返回抢占失败 return false; } } @@ -372,13 +378,13 @@ public sealed class EfTenantRepository(TakeoutAppDbContext context) : ITenantRep /// public Task AddAuditLogAsync(TenantAuditLog log, CancellationToken cancellationToken = default) { - return context.TenantAuditLogs.AddAsync(log, cancellationToken).AsTask(); + return logsContext.TenantAuditLogs.AddAsync(log, cancellationToken).AsTask(); } /// public async Task> GetAuditLogsAsync(long tenantId, CancellationToken cancellationToken = default) { - return await context.TenantAuditLogs + return await logsContext.TenantAuditLogs .IgnoreQueryFilters() .AsNoTracking() .Where(x => x.DeletedAt == null && x.TenantId == tenantId) @@ -387,8 +393,12 @@ public sealed class EfTenantRepository(TakeoutAppDbContext context) : ITenantRep } /// - public Task SaveChangesAsync(CancellationToken cancellationToken = default) + public async Task SaveChangesAsync(CancellationToken cancellationToken = default) { - return context.SaveChangesAsync(cancellationToken); + // 1. 保存业务库变更 + await context.SaveChangesAsync(cancellationToken); + + // 2. (空行后) 保存日志库变更 + await logsContext.SaveChangesAsync(cancellationToken); } } diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Logs/Persistence/TakeoutLogsDbContext.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Logs/Persistence/TakeoutLogsDbContext.cs new file mode 100644 index 0000000..1743a1e --- /dev/null +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Logs/Persistence/TakeoutLogsDbContext.cs @@ -0,0 +1,102 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using TakeoutSaaS.Domain.Membership.Entities; +using TakeoutSaaS.Domain.Merchants.Entities; +using TakeoutSaaS.Domain.Tenants.Entities; +using TakeoutSaaS.Infrastructure.Common.Persistence; +using TakeoutSaaS.Shared.Abstractions.Ids; +using TakeoutSaaS.Shared.Abstractions.Security; +using TakeoutSaaS.Shared.Abstractions.Tenancy; + +namespace TakeoutSaaS.Infrastructure.Logs.Persistence; + +/// +/// 日志库 DbContext。 +/// +public sealed class TakeoutLogsDbContext( + DbContextOptions options, + ITenantProvider tenantProvider, + ICurrentUserAccessor? currentUserAccessor = null, + IIdGenerator? idGenerator = null) + : TenantAwareDbContext(options, tenantProvider, currentUserAccessor, idGenerator) +{ + /// + /// 租户审计日志集合。 + /// + public DbSet TenantAuditLogs => Set(); + + /// + /// 商户审计日志集合。 + /// + public DbSet MerchantAuditLogs => Set(); + + /// + /// 运营操作日志集合。 + /// + public DbSet OperationLogs => Set(); + + /// + /// 成长值日志集合。 + /// + public DbSet MemberGrowthLogs => Set(); + + /// + /// 配置实体模型。 + /// + /// 模型构建器。 + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + ConfigureTenantAuditLog(modelBuilder.Entity()); + ConfigureMerchantAuditLog(modelBuilder.Entity()); + ConfigureOperationLog(modelBuilder.Entity()); + ConfigureMemberGrowthLog(modelBuilder.Entity()); + } + + private static void ConfigureTenantAuditLog(EntityTypeBuilder builder) + { + builder.ToTable("tenant_audit_logs"); + builder.HasKey(x => x.Id); + builder.Property(x => x.TenantId).IsRequired(); + builder.Property(x => x.Title).HasMaxLength(128).IsRequired(); + builder.Property(x => x.Description).HasMaxLength(1024); + builder.Property(x => x.OperatorName).HasMaxLength(64); + builder.HasIndex(x => x.TenantId); + } + + private static void ConfigureMerchantAuditLog(EntityTypeBuilder builder) + { + builder.ToTable("merchant_audit_logs"); + builder.HasKey(x => x.Id); + builder.Property(x => x.MerchantId).IsRequired(); + builder.Property(x => x.Title).HasMaxLength(128).IsRequired(); + builder.Property(x => x.Description).HasMaxLength(1024); + builder.Property(x => x.OperatorName).HasMaxLength(64); + builder.HasIndex(x => new { x.TenantId, x.MerchantId }); + } + + private static void ConfigureOperationLog(EntityTypeBuilder builder) + { + builder.ToTable("operation_logs"); + builder.HasKey(x => x.Id); + builder.Property(x => x.OperationType).HasMaxLength(64).IsRequired(); + builder.Property(x => x.TargetType).HasMaxLength(64).IsRequired(); + builder.Property(x => x.TargetIds).HasColumnType("text"); + builder.Property(x => x.OperatorId).HasMaxLength(64); + builder.Property(x => x.OperatorName).HasMaxLength(128); + builder.Property(x => x.Parameters).HasColumnType("text"); + builder.Property(x => x.Result).HasColumnType("text"); + builder.Property(x => x.Success).IsRequired(); + builder.HasIndex(x => new { x.OperationType, x.CreatedAt }); + builder.HasIndex(x => x.CreatedAt); + } + + private static void ConfigureMemberGrowthLog(EntityTypeBuilder builder) + { + builder.ToTable("member_growth_logs"); + builder.HasKey(x => x.Id); + builder.Property(x => x.MemberId).IsRequired(); + builder.Property(x => x.Notes).HasMaxLength(256); + builder.HasIndex(x => new { x.TenantId, x.MemberId, x.OccurredAt }); + } +} diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Logs/Persistence/TakeoutLogsDesignTimeDbContextFactory.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Logs/Persistence/TakeoutLogsDesignTimeDbContextFactory.cs new file mode 100644 index 0000000..5184dcf --- /dev/null +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Logs/Persistence/TakeoutLogsDesignTimeDbContextFactory.cs @@ -0,0 +1,35 @@ +using Microsoft.EntityFrameworkCore; +using TakeoutSaaS.Infrastructure.Common.Persistence.DesignTime; +using TakeoutSaaS.Shared.Abstractions.Constants; +using TakeoutSaaS.Shared.Abstractions.Security; +using TakeoutSaaS.Shared.Abstractions.Tenancy; + +namespace TakeoutSaaS.Infrastructure.Logs.Persistence; + +/// +/// 日志库设计时工厂,供 EF CLI 使用。 +/// +internal sealed class TakeoutLogsDesignTimeDbContextFactory + : DesignTimeDbContextFactoryBase +{ + /// + /// 初始化日志库设计时上下文工厂。 + /// + public TakeoutLogsDesignTimeDbContextFactory() + : base(DatabaseConstants.LogsDataSource, "TAKEOUTSAAS_LOGS_CONNECTION") + { + } + + /// + /// 创建日志库 DbContext。 + /// + /// 上下文选项。 + /// 租户提供器。 + /// 当前用户访问器。 + /// 日志库上下文实例。 + protected override TakeoutLogsDbContext CreateContext( + DbContextOptions options, + ITenantProvider tenantProvider, + ICurrentUserAccessor currentUserAccessor) + => new(options, tenantProvider, currentUserAccessor); +} diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/IdentityDb/20251220183000_GrantAnnouncementPermissionsToSuperAdmin.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/IdentityDb/20251220183000_GrantAnnouncementPermissionsToSuperAdmin.cs index a056adf..b01cb6e 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/IdentityDb/20251220183000_GrantAnnouncementPermissionsToSuperAdmin.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/IdentityDb/20251220183000_GrantAnnouncementPermissionsToSuperAdmin.cs @@ -52,8 +52,15 @@ SELECT NULL, NULL FROM target_permissions tp -ON CONFLICT (""TenantId"", ""Code"") DO NOTHING; - +ON CONFLICT (""TenantId"", ""Code"") DO NOTHING;" + ); + 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 +) INSERT INTO ""role_permissions"" ( ""TenantId"", ""RoleId"", diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/LogsDb/20251226091835_InitLogsDb.Designer.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/LogsDb/20251226091835_InitLogsDb.Designer.cs new file mode 100644 index 0000000..3adc7b7 --- /dev/null +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/LogsDb/20251226091835_InitLogsDb.Designer.cs @@ -0,0 +1,335 @@ +// +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.Logs.Persistence; + +#nullable disable + +namespace TakeoutSaaS.Infrastructure.Migrations.LogsDb +{ + [DbContext(typeof(TakeoutLogsDbContext))] + [Migration("20251226091835_InitLogsDb")] + partial class InitLogsDb + { + /// + 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.Membership.Entities.MemberGrowthLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChangeValue") + .HasColumnType("integer") + .HasComment("变动数量。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("CurrentValue") + .HasColumnType("integer") + .HasComment("当前成长值。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("MemberId") + .HasColumnType("bigint") + .HasComment("会员标识。"); + + b.Property("Notes") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("备注。"); + + b.Property("OccurredAt") + .HasColumnType("timestamp with time zone") + .HasComment("发生时间。"); + + b.Property("TenantId") + .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("TenantId", "MemberId", "OccurredAt"); + + b.ToTable("member_growth_logs", null, t => + { + t.HasComment("成长值变动日志。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Merchants.Entities.MerchantAuditLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("integer") + .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(1024) + .HasColumnType("character varying(1024)") + .HasComment("详情描述。"); + + b.Property("MerchantId") + .HasColumnType("bigint") + .HasComment("商户标识。"); + + b.Property("OperatorId") + .HasColumnType("bigint") + .HasComment("操作人 ID。"); + + b.Property("OperatorName") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("操作人名称。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + 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("TenantId", "MerchantId"); + + b.ToTable("merchant_audit_logs", null, t => + { + t.HasComment("商户入驻审核日志。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Tenants.Entities.OperationLog", 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("OperationType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("操作类型:BatchExtend, BatchRemind, StatusChange 等。"); + + b.Property("OperatorId") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("操作人ID。"); + + b.Property("OperatorName") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasComment("操作人名称。"); + + b.Property("Parameters") + .HasColumnType("text") + .HasComment("操作参数(JSON)。"); + + b.Property("Result") + .HasColumnType("text") + .HasComment("操作结果(JSON)。"); + + b.Property("Success") + .HasColumnType("boolean") + .HasComment("是否成功。"); + + b.Property("TargetIds") + .HasColumnType("text") + .HasComment("目标ID列表(JSON)。"); + + b.Property("TargetType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("目标类型:Subscription, Bill 等。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("OperationType", "CreatedAt"); + + b.ToTable("operation_logs", null, t => + { + t.HasComment("运营操作日志。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Tenants.Entities.TenantAuditLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("integer") + .HasComment("操作类型。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("CurrentStatus") + .HasColumnType("integer") + .HasComment("新状态。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("Description") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasComment("详细描述。"); + + b.Property("OperatorId") + .HasColumnType("bigint") + .HasComment("操作人 ID。"); + + b.Property("OperatorName") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("操作人名称。"); + + b.Property("PreviousStatus") + .HasColumnType("integer") + .HasComment("原状态。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .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("TenantId"); + + b.ToTable("tenant_audit_logs", null, t => + { + t.HasComment("租户运营审核日志。"); + }); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/LogsDb/20251226091835_InitLogsDb.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/LogsDb/20251226091835_InitLogsDb.cs new file mode 100644 index 0000000..fd7090a --- /dev/null +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/LogsDb/20251226091835_InitLogsDb.cs @@ -0,0 +1,21 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace TakeoutSaaS.Infrastructure.Migrations.LogsDb +{ + /// + public partial class InitLogsDb : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + } + } +} + diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/LogsDb/TakeoutLogsDbContextModelSnapshot.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/LogsDb/TakeoutLogsDbContextModelSnapshot.cs new file mode 100644 index 0000000..d35a5c9 --- /dev/null +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/LogsDb/TakeoutLogsDbContextModelSnapshot.cs @@ -0,0 +1,332 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using TakeoutSaaS.Infrastructure.Logs.Persistence; + +#nullable disable + +namespace TakeoutSaaS.Infrastructure.Migrations.LogsDb +{ + [DbContext(typeof(TakeoutLogsDbContext))] + partial class TakeoutLogsDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("TakeoutSaaS.Domain.Membership.Entities.MemberGrowthLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChangeValue") + .HasColumnType("integer") + .HasComment("变动数量。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("CurrentValue") + .HasColumnType("integer") + .HasComment("当前成长值。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("MemberId") + .HasColumnType("bigint") + .HasComment("会员标识。"); + + b.Property("Notes") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("备注。"); + + b.Property("OccurredAt") + .HasColumnType("timestamp with time zone") + .HasComment("发生时间。"); + + b.Property("TenantId") + .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("TenantId", "MemberId", "OccurredAt"); + + b.ToTable("member_growth_logs", null, t => + { + t.HasComment("成长值变动日志。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Merchants.Entities.MerchantAuditLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("integer") + .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(1024) + .HasColumnType("character varying(1024)") + .HasComment("详情描述。"); + + b.Property("MerchantId") + .HasColumnType("bigint") + .HasComment("商户标识。"); + + b.Property("OperatorId") + .HasColumnType("bigint") + .HasComment("操作人 ID。"); + + b.Property("OperatorName") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("操作人名称。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .HasComment("所属租户 ID。"); + + 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("TenantId", "MerchantId"); + + b.ToTable("merchant_audit_logs", null, t => + { + t.HasComment("商户入驻审核日志。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Tenants.Entities.OperationLog", 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("OperationType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("操作类型:BatchExtend, BatchRemind, StatusChange 等。"); + + b.Property("OperatorId") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("操作人ID。"); + + b.Property("OperatorName") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasComment("操作人名称。"); + + b.Property("Parameters") + .HasColumnType("text") + .HasComment("操作参数(JSON)。"); + + b.Property("Result") + .HasColumnType("text") + .HasComment("操作结果(JSON)。"); + + b.Property("Success") + .HasColumnType("boolean") + .HasComment("是否成功。"); + + b.Property("TargetIds") + .HasColumnType("text") + .HasComment("目标ID列表(JSON)。"); + + b.Property("TargetType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("目标类型:Subscription, Bill 等。"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); + + b.Property("UpdatedBy") + .HasColumnType("bigint") + .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("OperationType", "CreatedAt"); + + b.ToTable("operation_logs", null, t => + { + t.HasComment("运营操作日志。"); + }); + }); + + modelBuilder.Entity("TakeoutSaaS.Domain.Tenants.Entities.TenantAuditLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasComment("实体唯一标识。"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("integer") + .HasComment("操作类型。"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间(UTC)。"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasComment("创建人用户标识,匿名或系统操作时为 null。"); + + b.Property("CurrentStatus") + .HasColumnType("integer") + .HasComment("新状态。"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasComment("软删除时间(UTC),未删除时为 null。"); + + b.Property("DeletedBy") + .HasColumnType("bigint") + .HasComment("删除人用户标识(软删除),未删除时为 null。"); + + b.Property("Description") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasComment("详细描述。"); + + b.Property("OperatorId") + .HasColumnType("bigint") + .HasComment("操作人 ID。"); + + b.Property("OperatorName") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("操作人名称。"); + + b.Property("PreviousStatus") + .HasColumnType("integer") + .HasComment("原状态。"); + + b.Property("TenantId") + .HasColumnType("bigint") + .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("TenantId"); + + b.ToTable("tenant_audit_logs", null, t => + { + t.HasComment("租户运营审核日志。"); + }); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/TakeoutAppDbContextModelSnapshot.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/TakeoutAppDbContextModelSnapshot.cs index 4dfee4f..3d77367 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/TakeoutAppDbContextModelSnapshot.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Migrations/TakeoutAppDbContextModelSnapshot.cs @@ -2125,74 +2125,6 @@ namespace TakeoutSaaS.Infrastructure.Migrations }); }); - modelBuilder.Entity("TakeoutSaaS.Domain.Membership.Entities.MemberGrowthLog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint") - .HasComment("实体唯一标识。"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ChangeValue") - .HasColumnType("integer") - .HasComment("变动数量。"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasComment("创建时间(UTC)。"); - - b.Property("CreatedBy") - .HasColumnType("bigint") - .HasComment("创建人用户标识,匿名或系统操作时为 null。"); - - b.Property("CurrentValue") - .HasColumnType("integer") - .HasComment("当前成长值。"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasComment("软删除时间(UTC),未删除时为 null。"); - - b.Property("DeletedBy") - .HasColumnType("bigint") - .HasComment("删除人用户标识(软删除),未删除时为 null。"); - - b.Property("MemberId") - .HasColumnType("bigint") - .HasComment("会员标识。"); - - b.Property("Notes") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasComment("备注。"); - - b.Property("OccurredAt") - .HasColumnType("timestamp with time zone") - .HasComment("发生时间。"); - - b.Property("TenantId") - .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("TenantId", "MemberId", "OccurredAt"); - - b.ToTable("member_growth_logs", null, t => - { - t.HasComment("成长值变动日志。"); - }); - }); - modelBuilder.Entity("TakeoutSaaS.Domain.Membership.Entities.MemberPointLedger", b => { b.Property("Id") @@ -2575,81 +2507,6 @@ namespace TakeoutSaaS.Infrastructure.Migrations }); }); - modelBuilder.Entity("TakeoutSaaS.Domain.Merchants.Entities.MerchantAuditLog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint") - .HasComment("实体唯一标识。"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Action") - .HasColumnType("integer") - .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(1024) - .HasColumnType("character varying(1024)") - .HasComment("详情描述。"); - - b.Property("MerchantId") - .HasColumnType("bigint") - .HasComment("商户标识。"); - - b.Property("OperatorId") - .HasColumnType("bigint") - .HasComment("操作人 ID。"); - - b.Property("OperatorName") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasComment("操作人名称。"); - - b.Property("TenantId") - .HasColumnType("bigint") - .HasComment("所属租户 ID。"); - - 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("TenantId", "MerchantId"); - - b.ToTable("merchant_audit_logs", null, t => - { - t.HasComment("商户入驻审核日志。"); - }); - }); - modelBuilder.Entity("TakeoutSaaS.Domain.Merchants.Entities.MerchantCategory", b => { b.Property("Id") @@ -5680,89 +5537,6 @@ namespace TakeoutSaaS.Infrastructure.Migrations }); }); - modelBuilder.Entity("TakeoutSaaS.Domain.Tenants.Entities.OperationLog", 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("OperationType") - .IsRequired() - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasComment("操作类型:BatchExtend, BatchRemind, StatusChange 等。"); - - b.Property("OperatorId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasComment("操作人ID。"); - - b.Property("OperatorName") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasComment("操作人名称。"); - - b.Property("Parameters") - .HasColumnType("text") - .HasComment("操作参数(JSON)。"); - - b.Property("Result") - .HasColumnType("text") - .HasComment("操作结果(JSON)。"); - - b.Property("Success") - .HasColumnType("boolean") - .HasComment("是否成功。"); - - b.Property("TargetIds") - .HasColumnType("text") - .HasComment("目标ID列表(JSON)。"); - - b.Property("TargetType") - .IsRequired() - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasComment("目标类型:Subscription, Bill 等。"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasComment("最近一次更新时间(UTC),从未更新时为 null。"); - - b.Property("UpdatedBy") - .HasColumnType("bigint") - .HasComment("最后更新人用户标识,匿名或系统操作时为 null。"); - - b.HasKey("Id"); - - b.HasIndex("CreatedAt"); - - b.HasIndex("OperationType", "CreatedAt"); - - b.ToTable("operation_logs", null, t => - { - t.HasComment("运营操作日志。"); - }); - }); - modelBuilder.Entity("TakeoutSaaS.Domain.Tenants.Entities.QuotaPackage", b => { b.Property("Id") @@ -6176,85 +5950,6 @@ namespace TakeoutSaaS.Infrastructure.Migrations }); }); - modelBuilder.Entity("TakeoutSaaS.Domain.Tenants.Entities.TenantAuditLog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint") - .HasComment("实体唯一标识。"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Action") - .HasColumnType("integer") - .HasComment("操作类型。"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasComment("创建时间(UTC)。"); - - b.Property("CreatedBy") - .HasColumnType("bigint") - .HasComment("创建人用户标识,匿名或系统操作时为 null。"); - - b.Property("CurrentStatus") - .HasColumnType("integer") - .HasComment("新状态。"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasComment("软删除时间(UTC),未删除时为 null。"); - - b.Property("DeletedBy") - .HasColumnType("bigint") - .HasComment("删除人用户标识(软删除),未删除时为 null。"); - - b.Property("Description") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasComment("详细描述。"); - - b.Property("OperatorId") - .HasColumnType("bigint") - .HasComment("操作人 ID。"); - - b.Property("OperatorName") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasComment("操作人名称。"); - - b.Property("PreviousStatus") - .HasColumnType("integer") - .HasComment("原状态。"); - - b.Property("TenantId") - .HasColumnType("bigint") - .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("TenantId"); - - b.ToTable("tenant_audit_logs", null, t => - { - t.HasComment("租户运营审核日志。"); - }); - }); - modelBuilder.Entity("TakeoutSaaS.Domain.Tenants.Entities.TenantBillingStatement", b => { b.Property("Id")