feat: 身份操作日志改造为Outbox并修正日志库连接
This commit is contained in:
@@ -12,4 +12,9 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="System.IO.Packaging" Version="8.0.1" />
|
<PackageReference Include="System.IO.Packaging" Version="8.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup Condition="'$(MSBuildProjectName)' == 'TakeoutSaaS.Infrastructure'">
|
||||||
|
<PackageReference Include="MassTransit" Version="8.2.5" />
|
||||||
|
<PackageReference Include="MassTransit.EntityFrameworkCore" Version="8.2.5" />
|
||||||
|
<PackageReference Include="MassTransit.RabbitMQ" Version="8.2.5" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ using TakeoutSaaS.Application.Sms.Extensions;
|
|||||||
using TakeoutSaaS.Application.Storage.Extensions;
|
using TakeoutSaaS.Application.Storage.Extensions;
|
||||||
using TakeoutSaaS.Infrastructure.App.Extensions;
|
using TakeoutSaaS.Infrastructure.App.Extensions;
|
||||||
using TakeoutSaaS.Infrastructure.Identity.Extensions;
|
using TakeoutSaaS.Infrastructure.Identity.Extensions;
|
||||||
|
using TakeoutSaaS.Infrastructure.Logs.Extensions;
|
||||||
using TakeoutSaaS.Module.Authorization.Extensions;
|
using TakeoutSaaS.Module.Authorization.Extensions;
|
||||||
using TakeoutSaaS.Module.Dictionary.Extensions;
|
using TakeoutSaaS.Module.Dictionary.Extensions;
|
||||||
using TakeoutSaaS.Module.Messaging.Extensions;
|
using TakeoutSaaS.Module.Messaging.Extensions;
|
||||||
@@ -76,6 +77,7 @@ builder.Services.AddSmsModule(builder.Configuration);
|
|||||||
builder.Services.AddSmsApplication(builder.Configuration);
|
builder.Services.AddSmsApplication(builder.Configuration);
|
||||||
builder.Services.AddMessagingModule(builder.Configuration);
|
builder.Services.AddMessagingModule(builder.Configuration);
|
||||||
builder.Services.AddMessagingApplication();
|
builder.Services.AddMessagingApplication();
|
||||||
|
builder.Services.AddOperationLogOutbox(builder.Configuration);
|
||||||
builder.Services.AddSchedulerModule(builder.Configuration);
|
builder.Services.AddSchedulerModule(builder.Configuration);
|
||||||
builder.Services.AddHealthChecks();
|
builder.Services.AddHealthChecks();
|
||||||
builder.Services.AddRateLimiter(options =>
|
builder.Services.AddRateLimiter(options =>
|
||||||
|
|||||||
@@ -32,9 +32,9 @@
|
|||||||
"MaxRetryDelaySeconds": 5
|
"MaxRetryDelaySeconds": 5
|
||||||
},
|
},
|
||||||
"LogsDatabase": {
|
"LogsDatabase": {
|
||||||
"Write": "Host=120.53.222.17;Port=5432;Database=takeout_logs_db;Username=pg_roles;Password=Logs112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
"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": [
|
"Reads": [
|
||||||
"Host=120.53.222.17;Port=5432;Database=takeout_logs_db;Username=pg_roles;Password=Logs112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50"
|
"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,
|
"CommandTimeoutSeconds": 30,
|
||||||
"MaxRetryCount": 3,
|
"MaxRetryCount": 3,
|
||||||
|
|||||||
@@ -32,9 +32,9 @@
|
|||||||
"MaxRetryDelaySeconds": 5
|
"MaxRetryDelaySeconds": 5
|
||||||
},
|
},
|
||||||
"LogsDatabase": {
|
"LogsDatabase": {
|
||||||
"Write": "Host=120.53.222.17;Port=5432;Database=takeout_logs_db;Username=pg_roles;Password=Logs112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
"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": [
|
"Reads": [
|
||||||
"Host=120.53.222.17;Port=5432;Database=takeout_logs_db;Username=pg_roles;Password=Logs112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50"
|
"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,
|
"CommandTimeoutSeconds": 30,
|
||||||
"MaxRetryCount": 3,
|
"MaxRetryCount": 3,
|
||||||
|
|||||||
@@ -29,9 +29,9 @@
|
|||||||
"MaxRetryDelaySeconds": 5
|
"MaxRetryDelaySeconds": 5
|
||||||
},
|
},
|
||||||
"LogsDatabase": {
|
"LogsDatabase": {
|
||||||
"Write": "Host=120.53.222.17;Port=5432;Database=takeout_logs_db;Username=pg_roles;Password=Logs112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
"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": [
|
"Reads": [
|
||||||
"Host=120.53.222.17;Port=5432;Database=takeout_logs_db;Username=pg_roles;Password=Logs112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50"
|
"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,
|
"CommandTimeoutSeconds": 30,
|
||||||
"MaxRetryCount": 3,
|
"MaxRetryCount": 3,
|
||||||
|
|||||||
@@ -29,9 +29,9 @@
|
|||||||
"MaxRetryDelaySeconds": 5
|
"MaxRetryDelaySeconds": 5
|
||||||
},
|
},
|
||||||
"LogsDatabase": {
|
"LogsDatabase": {
|
||||||
"Write": "Host=120.53.222.17;Port=5432;Database=takeout_logs_db;Username=pg_roles;Password=Logs112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
"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": [
|
"Reads": [
|
||||||
"Host=120.53.222.17;Port=5432;Database=takeout_logs_db;Username=pg_roles;Password=Logs112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50"
|
"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,
|
"CommandTimeoutSeconds": 30,
|
||||||
"MaxRetryCount": 3,
|
"MaxRetryCount": 3,
|
||||||
|
|||||||
@@ -29,9 +29,9 @@
|
|||||||
"MaxRetryDelaySeconds": 5
|
"MaxRetryDelaySeconds": 5
|
||||||
},
|
},
|
||||||
"LogsDatabase": {
|
"LogsDatabase": {
|
||||||
"Write": "Host=120.53.222.17;Port=5432;Database=takeout_logs_db;Username=pg_roles;Password=Logs112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
"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": [
|
"Reads": [
|
||||||
"Host=120.53.222.17;Port=5432;Database=takeout_logs_db;Username=pg_roles;Password=Logs112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50"
|
"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,
|
"CommandTimeoutSeconds": 30,
|
||||||
"MaxRetryCount": 3,
|
"MaxRetryCount": 3,
|
||||||
|
|||||||
@@ -29,9 +29,9 @@
|
|||||||
"MaxRetryDelaySeconds": 5
|
"MaxRetryDelaySeconds": 5
|
||||||
},
|
},
|
||||||
"LogsDatabase": {
|
"LogsDatabase": {
|
||||||
"Write": "Host=120.53.222.17;Port=5432;Database=takeout_logs_db;Username=pg_roles;Password=Logs112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50",
|
"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": [
|
"Reads": [
|
||||||
"Host=120.53.222.17;Port=5432;Database=takeout_logs_db;Username=pg_roles;Password=Logs112233;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=50"
|
"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,
|
"CommandTimeoutSeconds": 30,
|
||||||
"MaxRetryCount": 3,
|
"MaxRetryCount": 3,
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using TakeoutSaaS.Application.Identity.Events;
|
||||||
|
|
||||||
|
namespace TakeoutSaaS.Application.Identity.Abstractions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 身份模块操作日志发布器。
|
||||||
|
/// </summary>
|
||||||
|
public interface IIdentityOperationLogPublisher
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 发布身份模块操作日志消息。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">操作日志消息。</param>
|
||||||
|
/// <param name="cancellationToken">取消标记。</param>
|
||||||
|
/// <returns>异步任务。</returns>
|
||||||
|
Task PublishAsync(IdentityUserOperationLogMessage message, CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
namespace TakeoutSaaS.Application.Identity.Events;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 身份用户操作日志消息。
|
||||||
|
/// </summary>
|
||||||
|
public sealed record IdentityUserOperationLogMessage
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 操作类型。
|
||||||
|
/// </summary>
|
||||||
|
public string OperationType { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 目标类型。
|
||||||
|
/// </summary>
|
||||||
|
public string TargetType { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 目标 ID 列表(JSON)。
|
||||||
|
/// </summary>
|
||||||
|
public string? TargetIds { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 操作人 ID。
|
||||||
|
/// </summary>
|
||||||
|
public string? OperatorId { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 操作人名称。
|
||||||
|
/// </summary>
|
||||||
|
public string? OperatorName { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 操作参数(JSON)。
|
||||||
|
/// </summary>
|
||||||
|
public string? Parameters { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 操作结果(JSON)。
|
||||||
|
/// </summary>
|
||||||
|
public string? Result { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否成功。
|
||||||
|
/// </summary>
|
||||||
|
public bool Success { get; init; }
|
||||||
|
}
|
||||||
@@ -3,12 +3,11 @@ using System.Text.Json;
|
|||||||
using TakeoutSaaS.Application.Identity.Abstractions;
|
using TakeoutSaaS.Application.Identity.Abstractions;
|
||||||
using TakeoutSaaS.Application.Identity.Commands;
|
using TakeoutSaaS.Application.Identity.Commands;
|
||||||
using TakeoutSaaS.Application.Identity.Contracts;
|
using TakeoutSaaS.Application.Identity.Contracts;
|
||||||
|
using TakeoutSaaS.Application.Identity.Events;
|
||||||
using TakeoutSaaS.Application.Identity.Models;
|
using TakeoutSaaS.Application.Identity.Models;
|
||||||
using TakeoutSaaS.Domain.Identity.Entities;
|
using TakeoutSaaS.Domain.Identity.Entities;
|
||||||
using TakeoutSaaS.Domain.Identity.Enums;
|
using TakeoutSaaS.Domain.Identity.Enums;
|
||||||
using TakeoutSaaS.Domain.Identity.Repositories;
|
using TakeoutSaaS.Domain.Identity.Repositories;
|
||||||
using TakeoutSaaS.Domain.Tenants.Entities;
|
|
||||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
|
||||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Security;
|
using TakeoutSaaS.Shared.Abstractions.Security;
|
||||||
@@ -26,7 +25,7 @@ public sealed class BatchIdentityUserOperationCommandHandler(
|
|||||||
ITenantProvider tenantProvider,
|
ITenantProvider tenantProvider,
|
||||||
ICurrentUserAccessor currentUserAccessor,
|
ICurrentUserAccessor currentUserAccessor,
|
||||||
IAdminAuthService adminAuthService,
|
IAdminAuthService adminAuthService,
|
||||||
IOperationLogRepository operationLogRepository)
|
IIdentityOperationLogPublisher operationLogPublisher)
|
||||||
: IRequestHandler<BatchIdentityUserOperationCommand, BatchIdentityUserOperationResult>
|
: IRequestHandler<BatchIdentityUserOperationCommand, BatchIdentityUserOperationResult>
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -208,7 +207,7 @@ public sealed class BatchIdentityUserOperationCommandHandler(
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 7. (空行后) 写入操作日志
|
// 7. (空行后) 构建操作日志消息
|
||||||
var operatorName = string.IsNullOrWhiteSpace(operatorProfile.DisplayName)
|
var operatorName = string.IsNullOrWhiteSpace(operatorProfile.DisplayName)
|
||||||
? operatorProfile.Account
|
? operatorProfile.Account
|
||||||
: operatorProfile.DisplayName;
|
: operatorProfile.DisplayName;
|
||||||
@@ -217,7 +216,7 @@ public sealed class BatchIdentityUserOperationCommandHandler(
|
|||||||
operatorName = $"user:{currentUserAccessor.UserId}";
|
operatorName = $"user:{currentUserAccessor.UserId}";
|
||||||
}
|
}
|
||||||
|
|
||||||
var log = new OperationLog
|
var logMessage = new IdentityUserOperationLogMessage
|
||||||
{
|
{
|
||||||
OperationType = "identity-user:batch",
|
OperationType = "identity-user:batch",
|
||||||
TargetType = "identity_user",
|
TargetType = "identity_user",
|
||||||
@@ -228,8 +227,10 @@ public sealed class BatchIdentityUserOperationCommandHandler(
|
|||||||
Result = JsonSerializer.Serialize(new { successCount, failureCount = failures.Count }),
|
Result = JsonSerializer.Serialize(new { successCount, failureCount = failures.Count }),
|
||||||
Success = failures.Count == 0
|
Success = failures.Count == 0
|
||||||
};
|
};
|
||||||
await operationLogRepository.AddAsync(log, cancellationToken);
|
|
||||||
await operationLogRepository.SaveChangesAsync(cancellationToken);
|
// 8. (空行后) 写入 Outbox 并保存变更
|
||||||
|
await operationLogPublisher.PublishAsync(logMessage, cancellationToken);
|
||||||
|
await identityUserRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
return new BatchIdentityUserOperationResult
|
return new BatchIdentityUserOperationResult
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,10 +2,9 @@ using MediatR;
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using TakeoutSaaS.Application.Identity.Abstractions;
|
using TakeoutSaaS.Application.Identity.Abstractions;
|
||||||
using TakeoutSaaS.Application.Identity.Commands;
|
using TakeoutSaaS.Application.Identity.Commands;
|
||||||
|
using TakeoutSaaS.Application.Identity.Events;
|
||||||
using TakeoutSaaS.Domain.Identity.Enums;
|
using TakeoutSaaS.Domain.Identity.Enums;
|
||||||
using TakeoutSaaS.Domain.Identity.Repositories;
|
using TakeoutSaaS.Domain.Identity.Repositories;
|
||||||
using TakeoutSaaS.Domain.Tenants.Entities;
|
|
||||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
|
||||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Security;
|
using TakeoutSaaS.Shared.Abstractions.Security;
|
||||||
@@ -23,7 +22,7 @@ public sealed class ChangeIdentityUserStatusCommandHandler(
|
|||||||
ITenantProvider tenantProvider,
|
ITenantProvider tenantProvider,
|
||||||
ICurrentUserAccessor currentUserAccessor,
|
ICurrentUserAccessor currentUserAccessor,
|
||||||
IAdminAuthService adminAuthService,
|
IAdminAuthService adminAuthService,
|
||||||
IOperationLogRepository operationLogRepository)
|
IIdentityOperationLogPublisher operationLogPublisher)
|
||||||
: IRequestHandler<ChangeIdentityUserStatusCommand, bool>
|
: IRequestHandler<ChangeIdentityUserStatusCommand, bool>
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -81,9 +80,7 @@ public sealed class ChangeIdentityUserStatusCommandHandler(
|
|||||||
throw new BusinessException(ErrorCodes.BadRequest, "无效的用户状态");
|
throw new BusinessException(ErrorCodes.BadRequest, "无效的用户状态");
|
||||||
}
|
}
|
||||||
|
|
||||||
await identityUserRepository.SaveChangesAsync(cancellationToken);
|
// 6. (空行后) 构建操作日志消息
|
||||||
|
|
||||||
// 6. (空行后) 写入操作日志
|
|
||||||
var operatorName = string.IsNullOrWhiteSpace(operatorProfile.DisplayName)
|
var operatorName = string.IsNullOrWhiteSpace(operatorProfile.DisplayName)
|
||||||
? operatorProfile.Account
|
? operatorProfile.Account
|
||||||
: operatorProfile.DisplayName;
|
: operatorProfile.DisplayName;
|
||||||
@@ -92,7 +89,7 @@ public sealed class ChangeIdentityUserStatusCommandHandler(
|
|||||||
operatorName = $"user:{currentUserAccessor.UserId}";
|
operatorName = $"user:{currentUserAccessor.UserId}";
|
||||||
}
|
}
|
||||||
|
|
||||||
var log = new OperationLog
|
var logMessage = new IdentityUserOperationLogMessage
|
||||||
{
|
{
|
||||||
OperationType = "identity-user:status-change",
|
OperationType = "identity-user:status-change",
|
||||||
TargetType = "identity_user",
|
TargetType = "identity_user",
|
||||||
@@ -109,8 +106,10 @@ public sealed class ChangeIdentityUserStatusCommandHandler(
|
|||||||
Result = JsonSerializer.Serialize(new { userId = user.Id }),
|
Result = JsonSerializer.Serialize(new { userId = user.Id }),
|
||||||
Success = true
|
Success = true
|
||||||
};
|
};
|
||||||
await operationLogRepository.AddAsync(log, cancellationToken);
|
|
||||||
await operationLogRepository.SaveChangesAsync(cancellationToken);
|
// 7. (空行后) 写入 Outbox 并保存变更
|
||||||
|
await operationLogPublisher.PublishAsync(logMessage, cancellationToken);
|
||||||
|
await identityUserRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ using System.Text.Json;
|
|||||||
using TakeoutSaaS.Application.Identity.Abstractions;
|
using TakeoutSaaS.Application.Identity.Abstractions;
|
||||||
using TakeoutSaaS.Application.Identity.Commands;
|
using TakeoutSaaS.Application.Identity.Commands;
|
||||||
using TakeoutSaaS.Application.Identity.Contracts;
|
using TakeoutSaaS.Application.Identity.Contracts;
|
||||||
|
using TakeoutSaaS.Application.Identity.Events;
|
||||||
using TakeoutSaaS.Application.Identity.Queries;
|
using TakeoutSaaS.Application.Identity.Queries;
|
||||||
using TakeoutSaaS.Domain.Identity.Entities;
|
using TakeoutSaaS.Domain.Identity.Entities;
|
||||||
using TakeoutSaaS.Domain.Identity.Enums;
|
using TakeoutSaaS.Domain.Identity.Enums;
|
||||||
using TakeoutSaaS.Domain.Identity.Repositories;
|
using TakeoutSaaS.Domain.Identity.Repositories;
|
||||||
using TakeoutSaaS.Domain.Tenants.Entities;
|
|
||||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
|
||||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||||
|
using TakeoutSaaS.Shared.Abstractions.Ids;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Security;
|
using TakeoutSaaS.Shared.Abstractions.Security;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||||
|
|
||||||
@@ -28,7 +28,8 @@ public sealed class CreateIdentityUserCommandHandler(
|
|||||||
ITenantProvider tenantProvider,
|
ITenantProvider tenantProvider,
|
||||||
ICurrentUserAccessor currentUserAccessor,
|
ICurrentUserAccessor currentUserAccessor,
|
||||||
IAdminAuthService adminAuthService,
|
IAdminAuthService adminAuthService,
|
||||||
IOperationLogRepository operationLogRepository,
|
IIdentityOperationLogPublisher operationLogPublisher,
|
||||||
|
IIdGenerator idGenerator,
|
||||||
IMediator mediator)
|
IMediator mediator)
|
||||||
: IRequestHandler<CreateIdentityUserCommand, UserDetailDto>
|
: IRequestHandler<CreateIdentityUserCommand, UserDetailDto>
|
||||||
{
|
{
|
||||||
@@ -85,6 +86,7 @@ public sealed class CreateIdentityUserCommandHandler(
|
|||||||
// 6. (空行后) 创建用户实体
|
// 6. (空行后) 创建用户实体
|
||||||
var user = new IdentityUser
|
var user = new IdentityUser
|
||||||
{
|
{
|
||||||
|
Id = idGenerator.NextId(),
|
||||||
TenantId = tenantId,
|
TenantId = tenantId,
|
||||||
Account = account,
|
Account = account,
|
||||||
DisplayName = displayName,
|
DisplayName = displayName,
|
||||||
@@ -100,17 +102,7 @@ public sealed class CreateIdentityUserCommandHandler(
|
|||||||
};
|
};
|
||||||
user.PasswordHash = passwordHasher.HashPassword(user, request.Password);
|
user.PasswordHash = passwordHasher.HashPassword(user, request.Password);
|
||||||
|
|
||||||
// 7. (空行后) 持久化用户
|
// 7. (空行后) 构建操作日志消息
|
||||||
await identityUserRepository.AddAsync(user, cancellationToken);
|
|
||||||
await identityUserRepository.SaveChangesAsync(cancellationToken);
|
|
||||||
|
|
||||||
// 8. (空行后) 绑定角色
|
|
||||||
if (roleIds.Length > 0)
|
|
||||||
{
|
|
||||||
await userRoleRepository.ReplaceUserRolesAsync(tenantId, user.Id, roleIds, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 9. (空行后) 写入操作日志
|
|
||||||
var operatorName = string.IsNullOrWhiteSpace(operatorProfile.DisplayName)
|
var operatorName = string.IsNullOrWhiteSpace(operatorProfile.DisplayName)
|
||||||
? operatorProfile.Account
|
? operatorProfile.Account
|
||||||
: operatorProfile.DisplayName;
|
: operatorProfile.DisplayName;
|
||||||
@@ -119,7 +111,7 @@ public sealed class CreateIdentityUserCommandHandler(
|
|||||||
operatorName = $"user:{currentUserAccessor.UserId}";
|
operatorName = $"user:{currentUserAccessor.UserId}";
|
||||||
}
|
}
|
||||||
|
|
||||||
var log = new OperationLog
|
var logMessage = new IdentityUserOperationLogMessage
|
||||||
{
|
{
|
||||||
OperationType = "identity-user:create",
|
OperationType = "identity-user:create",
|
||||||
TargetType = "identity_user",
|
TargetType = "identity_user",
|
||||||
@@ -138,8 +130,17 @@ public sealed class CreateIdentityUserCommandHandler(
|
|||||||
Result = JsonSerializer.Serialize(new { userId = user.Id }),
|
Result = JsonSerializer.Serialize(new { userId = user.Id }),
|
||||||
Success = true
|
Success = true
|
||||||
};
|
};
|
||||||
await operationLogRepository.AddAsync(log, cancellationToken);
|
|
||||||
await operationLogRepository.SaveChangesAsync(cancellationToken);
|
// 8. (空行后) 持久化用户并写入 Outbox
|
||||||
|
await identityUserRepository.AddAsync(user, cancellationToken);
|
||||||
|
await operationLogPublisher.PublishAsync(logMessage, cancellationToken);
|
||||||
|
await identityUserRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
// 9. (空行后) 绑定角色
|
||||||
|
if (roleIds.Length > 0)
|
||||||
|
{
|
||||||
|
await userRoleRepository.ReplaceUserRolesAsync(tenantId, user.Id, roleIds, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
// 10. (空行后) 返回用户详情
|
// 10. (空行后) 返回用户详情
|
||||||
var detail = await mediator.Send(new GetIdentityUserDetailQuery { UserId = user.Id }, cancellationToken);
|
var detail = await mediator.Send(new GetIdentityUserDetailQuery { UserId = user.Id }, cancellationToken);
|
||||||
|
|||||||
@@ -2,10 +2,9 @@ using MediatR;
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using TakeoutSaaS.Application.Identity.Abstractions;
|
using TakeoutSaaS.Application.Identity.Abstractions;
|
||||||
using TakeoutSaaS.Application.Identity.Commands;
|
using TakeoutSaaS.Application.Identity.Commands;
|
||||||
|
using TakeoutSaaS.Application.Identity.Events;
|
||||||
using TakeoutSaaS.Domain.Identity.Enums;
|
using TakeoutSaaS.Domain.Identity.Enums;
|
||||||
using TakeoutSaaS.Domain.Identity.Repositories;
|
using TakeoutSaaS.Domain.Identity.Repositories;
|
||||||
using TakeoutSaaS.Domain.Tenants.Entities;
|
|
||||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
|
||||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Security;
|
using TakeoutSaaS.Shared.Abstractions.Security;
|
||||||
@@ -23,7 +22,7 @@ public sealed class DeleteIdentityUserCommandHandler(
|
|||||||
ITenantProvider tenantProvider,
|
ITenantProvider tenantProvider,
|
||||||
ICurrentUserAccessor currentUserAccessor,
|
ICurrentUserAccessor currentUserAccessor,
|
||||||
IAdminAuthService adminAuthService,
|
IAdminAuthService adminAuthService,
|
||||||
IOperationLogRepository operationLogRepository)
|
IIdentityOperationLogPublisher operationLogPublisher)
|
||||||
: IRequestHandler<DeleteIdentityUserCommand, bool>
|
: IRequestHandler<DeleteIdentityUserCommand, bool>
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -60,11 +59,7 @@ public sealed class DeleteIdentityUserCommandHandler(
|
|||||||
await EnsureNotLastActiveTenantAdminAsync(user.TenantId, user.Id, isSuperAdmin, cancellationToken);
|
await EnsureNotLastActiveTenantAdminAsync(user.TenantId, user.Id, isSuperAdmin, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. (空行后) 软删除用户
|
// 5. (空行后) 构建操作日志消息
|
||||||
await identityUserRepository.RemoveAsync(user, cancellationToken);
|
|
||||||
await identityUserRepository.SaveChangesAsync(cancellationToken);
|
|
||||||
|
|
||||||
// 6. (空行后) 写入操作日志
|
|
||||||
var operatorName = string.IsNullOrWhiteSpace(operatorProfile.DisplayName)
|
var operatorName = string.IsNullOrWhiteSpace(operatorProfile.DisplayName)
|
||||||
? operatorProfile.Account
|
? operatorProfile.Account
|
||||||
: operatorProfile.DisplayName;
|
: operatorProfile.DisplayName;
|
||||||
@@ -73,7 +68,7 @@ public sealed class DeleteIdentityUserCommandHandler(
|
|||||||
operatorName = $"user:{currentUserAccessor.UserId}";
|
operatorName = $"user:{currentUserAccessor.UserId}";
|
||||||
}
|
}
|
||||||
|
|
||||||
var log = new OperationLog
|
var logMessage = new IdentityUserOperationLogMessage
|
||||||
{
|
{
|
||||||
OperationType = "identity-user:delete",
|
OperationType = "identity-user:delete",
|
||||||
TargetType = "identity_user",
|
TargetType = "identity_user",
|
||||||
@@ -84,8 +79,11 @@ public sealed class DeleteIdentityUserCommandHandler(
|
|||||||
Result = JsonSerializer.Serialize(new { userId = user.Id }),
|
Result = JsonSerializer.Serialize(new { userId = user.Id }),
|
||||||
Success = true
|
Success = true
|
||||||
};
|
};
|
||||||
await operationLogRepository.AddAsync(log, cancellationToken);
|
|
||||||
await operationLogRepository.SaveChangesAsync(cancellationToken);
|
// 6. (空行后) 软删除用户并写入 Outbox
|
||||||
|
await identityUserRepository.RemoveAsync(user, cancellationToken);
|
||||||
|
await operationLogPublisher.PublishAsync(logMessage, cancellationToken);
|
||||||
|
await identityUserRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,9 @@ using System.Text.Json;
|
|||||||
using TakeoutSaaS.Application.Identity.Abstractions;
|
using TakeoutSaaS.Application.Identity.Abstractions;
|
||||||
using TakeoutSaaS.Application.Identity.Commands;
|
using TakeoutSaaS.Application.Identity.Commands;
|
||||||
using TakeoutSaaS.Application.Identity.Contracts;
|
using TakeoutSaaS.Application.Identity.Contracts;
|
||||||
|
using TakeoutSaaS.Application.Identity.Events;
|
||||||
using TakeoutSaaS.Domain.Identity.Enums;
|
using TakeoutSaaS.Domain.Identity.Enums;
|
||||||
using TakeoutSaaS.Domain.Identity.Repositories;
|
using TakeoutSaaS.Domain.Identity.Repositories;
|
||||||
using TakeoutSaaS.Domain.Tenants.Entities;
|
|
||||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
|
||||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Security;
|
using TakeoutSaaS.Shared.Abstractions.Security;
|
||||||
@@ -23,7 +22,7 @@ public sealed class ResetIdentityUserPasswordCommandHandler(
|
|||||||
ITenantProvider tenantProvider,
|
ITenantProvider tenantProvider,
|
||||||
ICurrentUserAccessor currentUserAccessor,
|
ICurrentUserAccessor currentUserAccessor,
|
||||||
IAdminAuthService adminAuthService,
|
IAdminAuthService adminAuthService,
|
||||||
IOperationLogRepository operationLogRepository)
|
IIdentityOperationLogPublisher operationLogPublisher)
|
||||||
: IRequestHandler<ResetIdentityUserPasswordCommand, ResetIdentityUserPasswordResult>
|
: IRequestHandler<ResetIdentityUserPasswordCommand, ResetIdentityUserPasswordResult>
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -66,9 +65,8 @@ public sealed class ResetIdentityUserPasswordCommandHandler(
|
|||||||
{
|
{
|
||||||
user.Status = IdentityUserStatus.Active;
|
user.Status = IdentityUserStatus.Active;
|
||||||
}
|
}
|
||||||
await identityUserRepository.SaveChangesAsync(cancellationToken);
|
|
||||||
|
|
||||||
// 6. (空行后) 写入操作日志
|
// 6. (空行后) 构建操作日志消息
|
||||||
var operatorName = string.IsNullOrWhiteSpace(operatorProfile.DisplayName)
|
var operatorName = string.IsNullOrWhiteSpace(operatorProfile.DisplayName)
|
||||||
? operatorProfile.Account
|
? operatorProfile.Account
|
||||||
: operatorProfile.DisplayName;
|
: operatorProfile.DisplayName;
|
||||||
@@ -77,7 +75,7 @@ public sealed class ResetIdentityUserPasswordCommandHandler(
|
|||||||
operatorName = $"user:{currentUserAccessor.UserId}";
|
operatorName = $"user:{currentUserAccessor.UserId}";
|
||||||
}
|
}
|
||||||
|
|
||||||
var log = new OperationLog
|
var logMessage = new IdentityUserOperationLogMessage
|
||||||
{
|
{
|
||||||
OperationType = "identity-user:password-reset",
|
OperationType = "identity-user:password-reset",
|
||||||
TargetType = "identity_user",
|
TargetType = "identity_user",
|
||||||
@@ -88,8 +86,10 @@ public sealed class ResetIdentityUserPasswordCommandHandler(
|
|||||||
Result = JsonSerializer.Serialize(new { userId = user.Id, expiresAt }),
|
Result = JsonSerializer.Serialize(new { userId = user.Id, expiresAt }),
|
||||||
Success = true
|
Success = true
|
||||||
};
|
};
|
||||||
await operationLogRepository.AddAsync(log, cancellationToken);
|
|
||||||
await operationLogRepository.SaveChangesAsync(cancellationToken);
|
// 7. (空行后) 写入 Outbox 并保存变更
|
||||||
|
await operationLogPublisher.PublishAsync(logMessage, cancellationToken);
|
||||||
|
await identityUserRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
return new ResetIdentityUserPasswordResult
|
return new ResetIdentityUserPasswordResult
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,9 +2,8 @@ using MediatR;
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using TakeoutSaaS.Application.Identity.Abstractions;
|
using TakeoutSaaS.Application.Identity.Abstractions;
|
||||||
using TakeoutSaaS.Application.Identity.Commands;
|
using TakeoutSaaS.Application.Identity.Commands;
|
||||||
|
using TakeoutSaaS.Application.Identity.Events;
|
||||||
using TakeoutSaaS.Domain.Identity.Repositories;
|
using TakeoutSaaS.Domain.Identity.Repositories;
|
||||||
using TakeoutSaaS.Domain.Tenants.Entities;
|
|
||||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
|
||||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Security;
|
using TakeoutSaaS.Shared.Abstractions.Security;
|
||||||
@@ -20,7 +19,7 @@ public sealed class RestoreIdentityUserCommandHandler(
|
|||||||
ITenantProvider tenantProvider,
|
ITenantProvider tenantProvider,
|
||||||
ICurrentUserAccessor currentUserAccessor,
|
ICurrentUserAccessor currentUserAccessor,
|
||||||
IAdminAuthService adminAuthService,
|
IAdminAuthService adminAuthService,
|
||||||
IOperationLogRepository operationLogRepository)
|
IIdentityOperationLogPublisher operationLogPublisher)
|
||||||
: IRequestHandler<RestoreIdentityUserCommand, bool>
|
: IRequestHandler<RestoreIdentityUserCommand, bool>
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -54,12 +53,7 @@ public sealed class RestoreIdentityUserCommandHandler(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. (空行后) 恢复软删除状态
|
// 4. (空行后) 构建操作日志消息
|
||||||
user.DeletedAt = null;
|
|
||||||
user.DeletedBy = null;
|
|
||||||
await identityUserRepository.SaveChangesAsync(cancellationToken);
|
|
||||||
|
|
||||||
// 5. (空行后) 写入操作日志
|
|
||||||
var operatorName = string.IsNullOrWhiteSpace(operatorProfile.DisplayName)
|
var operatorName = string.IsNullOrWhiteSpace(operatorProfile.DisplayName)
|
||||||
? operatorProfile.Account
|
? operatorProfile.Account
|
||||||
: operatorProfile.DisplayName;
|
: operatorProfile.DisplayName;
|
||||||
@@ -68,7 +62,7 @@ public sealed class RestoreIdentityUserCommandHandler(
|
|||||||
operatorName = $"user:{currentUserAccessor.UserId}";
|
operatorName = $"user:{currentUserAccessor.UserId}";
|
||||||
}
|
}
|
||||||
|
|
||||||
var log = new OperationLog
|
var logMessage = new IdentityUserOperationLogMessage
|
||||||
{
|
{
|
||||||
OperationType = "identity-user:restore",
|
OperationType = "identity-user:restore",
|
||||||
TargetType = "identity_user",
|
TargetType = "identity_user",
|
||||||
@@ -79,8 +73,12 @@ public sealed class RestoreIdentityUserCommandHandler(
|
|||||||
Result = JsonSerializer.Serialize(new { userId = user.Id }),
|
Result = JsonSerializer.Serialize(new { userId = user.Id }),
|
||||||
Success = true
|
Success = true
|
||||||
};
|
};
|
||||||
await operationLogRepository.AddAsync(log, cancellationToken);
|
|
||||||
await operationLogRepository.SaveChangesAsync(cancellationToken);
|
// 5. (空行后) 恢复软删除状态并写入 Outbox
|
||||||
|
user.DeletedAt = null;
|
||||||
|
user.DeletedBy = null;
|
||||||
|
await operationLogPublisher.PublishAsync(logMessage, cancellationToken);
|
||||||
|
await identityUserRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,10 @@ using System.Text.Json;
|
|||||||
using TakeoutSaaS.Application.Identity.Abstractions;
|
using TakeoutSaaS.Application.Identity.Abstractions;
|
||||||
using TakeoutSaaS.Application.Identity.Commands;
|
using TakeoutSaaS.Application.Identity.Commands;
|
||||||
using TakeoutSaaS.Application.Identity.Contracts;
|
using TakeoutSaaS.Application.Identity.Contracts;
|
||||||
|
using TakeoutSaaS.Application.Identity.Events;
|
||||||
using TakeoutSaaS.Application.Identity.Queries;
|
using TakeoutSaaS.Application.Identity.Queries;
|
||||||
using TakeoutSaaS.Domain.Identity.Entities;
|
using TakeoutSaaS.Domain.Identity.Entities;
|
||||||
using TakeoutSaaS.Domain.Identity.Repositories;
|
using TakeoutSaaS.Domain.Identity.Repositories;
|
||||||
using TakeoutSaaS.Domain.Tenants.Entities;
|
|
||||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
|
||||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Security;
|
using TakeoutSaaS.Shared.Abstractions.Security;
|
||||||
@@ -25,7 +24,7 @@ public sealed class UpdateIdentityUserCommandHandler(
|
|||||||
ITenantProvider tenantProvider,
|
ITenantProvider tenantProvider,
|
||||||
ICurrentUserAccessor currentUserAccessor,
|
ICurrentUserAccessor currentUserAccessor,
|
||||||
IAdminAuthService adminAuthService,
|
IAdminAuthService adminAuthService,
|
||||||
IOperationLogRepository operationLogRepository,
|
IIdentityOperationLogPublisher operationLogPublisher,
|
||||||
IMediator mediator)
|
IMediator mediator)
|
||||||
: IRequestHandler<UpdateIdentityUserCommand, UserDetailDto?>
|
: IRequestHandler<UpdateIdentityUserCommand, UserDetailDto?>
|
||||||
{
|
{
|
||||||
@@ -93,23 +92,7 @@ public sealed class UpdateIdentityUserCommandHandler(
|
|||||||
user.Avatar = string.IsNullOrWhiteSpace(request.Avatar) ? null : request.Avatar.Trim();
|
user.Avatar = string.IsNullOrWhiteSpace(request.Avatar) ? null : request.Avatar.Trim();
|
||||||
user.RowVersion = request.RowVersion;
|
user.RowVersion = request.RowVersion;
|
||||||
|
|
||||||
// 6. (空行后) 持久化用户更新
|
// 6. (空行后) 构建操作日志消息
|
||||||
try
|
|
||||||
{
|
|
||||||
await identityUserRepository.SaveChangesAsync(cancellationToken);
|
|
||||||
}
|
|
||||||
catch (Exception ex) when (IsConcurrencyException(ex))
|
|
||||||
{
|
|
||||||
throw new BusinessException(ErrorCodes.Conflict, "用户数据已被修改,请刷新后重试");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 7. (空行后) 覆盖角色绑定(仅当显式传入时)
|
|
||||||
if (roleIds != null)
|
|
||||||
{
|
|
||||||
await userRoleRepository.ReplaceUserRolesAsync(user.TenantId, user.Id, roleIds, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 8. (空行后) 写入操作日志
|
|
||||||
var operatorName = string.IsNullOrWhiteSpace(operatorProfile.DisplayName)
|
var operatorName = string.IsNullOrWhiteSpace(operatorProfile.DisplayName)
|
||||||
? operatorProfile.Account
|
? operatorProfile.Account
|
||||||
: operatorProfile.DisplayName;
|
: operatorProfile.DisplayName;
|
||||||
@@ -118,7 +101,7 @@ public sealed class UpdateIdentityUserCommandHandler(
|
|||||||
operatorName = $"user:{currentUserAccessor.UserId}";
|
operatorName = $"user:{currentUserAccessor.UserId}";
|
||||||
}
|
}
|
||||||
|
|
||||||
var log = new OperationLog
|
var logMessage = new IdentityUserOperationLogMessage
|
||||||
{
|
{
|
||||||
OperationType = "identity-user:update",
|
OperationType = "identity-user:update",
|
||||||
TargetType = "identity_user",
|
TargetType = "identity_user",
|
||||||
@@ -136,8 +119,23 @@ public sealed class UpdateIdentityUserCommandHandler(
|
|||||||
Result = JsonSerializer.Serialize(new { userId = user.Id }),
|
Result = JsonSerializer.Serialize(new { userId = user.Id }),
|
||||||
Success = true
|
Success = true
|
||||||
};
|
};
|
||||||
await operationLogRepository.AddAsync(log, cancellationToken);
|
|
||||||
await operationLogRepository.SaveChangesAsync(cancellationToken);
|
// 7. (空行后) 持久化用户更新并写入 Outbox
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await operationLogPublisher.PublishAsync(logMessage, cancellationToken);
|
||||||
|
await identityUserRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (IsConcurrencyException(ex))
|
||||||
|
{
|
||||||
|
throw new BusinessException(ErrorCodes.Conflict, "用户数据已被修改,请刷新后重试");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8. (空行后) 覆盖角色绑定(仅当显式传入时)
|
||||||
|
if (roleIds != null)
|
||||||
|
{
|
||||||
|
await userRoleRepository.ReplaceUserRolesAsync(user.TenantId, user.Id, roleIds, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
// 9. (空行后) 返回用户详情
|
// 9. (空行后) 返回用户详情
|
||||||
return await mediator.Send(new GetIdentityUserDetailQuery { UserId = user.Id }, cancellationToken);
|
return await mediator.Send(new GetIdentityUserDetailQuery { UserId = user.Id }, cancellationToken);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using TakeoutSaaS.Infrastructure.Identity.Options;
|
|||||||
using TakeoutSaaS.Infrastructure.Identity.Persistence;
|
using TakeoutSaaS.Infrastructure.Identity.Persistence;
|
||||||
using TakeoutSaaS.Infrastructure.Identity.Repositories;
|
using TakeoutSaaS.Infrastructure.Identity.Repositories;
|
||||||
using TakeoutSaaS.Infrastructure.Identity.Services;
|
using TakeoutSaaS.Infrastructure.Identity.Services;
|
||||||
|
using TakeoutSaaS.Infrastructure.Logs.Publishers;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||||
using DomainIdentityUser = TakeoutSaaS.Domain.Identity.Entities.IdentityUser;
|
using DomainIdentityUser = TakeoutSaaS.Domain.Identity.Entities.IdentityUser;
|
||||||
|
|
||||||
@@ -60,6 +61,7 @@ public static class ServiceCollectionExtensions
|
|||||||
services.AddScoped<IAdminPasswordResetTokenStore, RedisAdminPasswordResetTokenStore>();
|
services.AddScoped<IAdminPasswordResetTokenStore, RedisAdminPasswordResetTokenStore>();
|
||||||
services.AddScoped<ILoginRateLimiter, RedisLoginRateLimiter>();
|
services.AddScoped<ILoginRateLimiter, RedisLoginRateLimiter>();
|
||||||
services.AddScoped<IPasswordHasher<DomainIdentityUser>, PasswordHasher<DomainIdentityUser>>();
|
services.AddScoped<IPasswordHasher<DomainIdentityUser>, PasswordHasher<DomainIdentityUser>>();
|
||||||
|
services.AddScoped<IIdentityOperationLogPublisher, IdentityOperationLogPublisher>();
|
||||||
|
|
||||||
services.AddOptions<JwtOptions>()
|
services.AddOptions<JwtOptions>()
|
||||||
.Bind(configuration.GetSection("Identity:Jwt"))
|
.Bind(configuration.GetSection("Identity:Jwt"))
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using MassTransit;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
using TakeoutSaaS.Domain.Identity.Entities;
|
using TakeoutSaaS.Domain.Identity.Entities;
|
||||||
@@ -79,6 +80,8 @@ public sealed class IdentityDbContext(
|
|||||||
ConfigureUserRole(modelBuilder.Entity<UserRole>());
|
ConfigureUserRole(modelBuilder.Entity<UserRole>());
|
||||||
ConfigureRolePermission(modelBuilder.Entity<RolePermission>());
|
ConfigureRolePermission(modelBuilder.Entity<RolePermission>());
|
||||||
ConfigureMenuDefinition(modelBuilder.Entity<MenuDefinition>());
|
ConfigureMenuDefinition(modelBuilder.Entity<MenuDefinition>());
|
||||||
|
modelBuilder.AddOutboxMessageEntity();
|
||||||
|
modelBuilder.AddOutboxStateEntity();
|
||||||
ApplyTenantQueryFilters(modelBuilder);
|
ApplyTenantQueryFilters(modelBuilder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
using MassTransit;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Npgsql;
|
||||||
|
using TakeoutSaaS.Application.Identity.Events;
|
||||||
|
using TakeoutSaaS.Domain.Tenants.Entities;
|
||||||
|
using TakeoutSaaS.Infrastructure.Logs.Persistence;
|
||||||
|
|
||||||
|
namespace TakeoutSaaS.Infrastructure.Logs.Consumers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 身份用户操作日志消费者。
|
||||||
|
/// </summary>
|
||||||
|
public sealed class IdentityUserOperationLogConsumer(TakeoutLogsDbContext logsContext) : IConsumer<IdentityUserOperationLogMessage>
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task Consume(ConsumeContext<IdentityUserOperationLogMessage> context)
|
||||||
|
{
|
||||||
|
// 1. 校验消息标识并进行幂等检查
|
||||||
|
var messageId = context.MessageId;
|
||||||
|
if (!messageId.HasValue)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("缺少 MessageId,无法进行日志幂等处理。");
|
||||||
|
}
|
||||||
|
|
||||||
|
var exists = await logsContext.OperationLogInboxMessages
|
||||||
|
.AsNoTracking()
|
||||||
|
.AnyAsync(x => x.MessageId == messageId.Value, context.CancellationToken);
|
||||||
|
if (exists)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. (空行后) 构建日志实体与去重记录
|
||||||
|
var message = context.Message;
|
||||||
|
var log = new OperationLog
|
||||||
|
{
|
||||||
|
OperationType = message.OperationType,
|
||||||
|
TargetType = message.TargetType,
|
||||||
|
TargetIds = message.TargetIds,
|
||||||
|
OperatorId = message.OperatorId,
|
||||||
|
OperatorName = message.OperatorName,
|
||||||
|
Parameters = message.Parameters,
|
||||||
|
Result = message.Result,
|
||||||
|
Success = message.Success
|
||||||
|
};
|
||||||
|
logsContext.OperationLogInboxMessages.Add(new OperationLogInboxMessage
|
||||||
|
{
|
||||||
|
MessageId = messageId.Value,
|
||||||
|
ConsumedAt = DateTime.UtcNow
|
||||||
|
});
|
||||||
|
logsContext.OperationLogs.Add(log);
|
||||||
|
|
||||||
|
// 3. (空行后) 保存并处理并发去重冲突
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await logsContext.SaveChangesAsync(context.CancellationToken);
|
||||||
|
}
|
||||||
|
catch (DbUpdateException ex) when (IsDuplicateMessage(ex))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsDuplicateMessage(DbUpdateException exception)
|
||||||
|
{
|
||||||
|
if (exception.InnerException is PostgresException postgresException)
|
||||||
|
{
|
||||||
|
return postgresException.SqlState == PostgresErrorCodes.UniqueViolation;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
using MassTransit;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using TakeoutSaaS.Infrastructure.Identity.Persistence;
|
||||||
|
using TakeoutSaaS.Infrastructure.Logs.Consumers;
|
||||||
|
using TakeoutSaaS.Module.Messaging.Options;
|
||||||
|
|
||||||
|
namespace TakeoutSaaS.Infrastructure.Logs.Extensions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 操作日志 Outbox 注册扩展。
|
||||||
|
/// </summary>
|
||||||
|
public static class OperationLogOutboxServiceCollectionExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 注册操作日志 Outbox 与消费者。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="services">服务集合。</param>
|
||||||
|
/// <param name="configuration">配置源。</param>
|
||||||
|
/// <returns>服务集合。</returns>
|
||||||
|
public static IServiceCollection AddOperationLogOutbox(this IServiceCollection services, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
// 1. 读取 RabbitMQ 配置
|
||||||
|
var options = configuration.GetSection("RabbitMQ").Get<RabbitMqOptions>();
|
||||||
|
if (options == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("缺少 RabbitMQ 配置。");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. (空行后) 注册 MassTransit 与 Outbox
|
||||||
|
services.AddMassTransit(configurator =>
|
||||||
|
{
|
||||||
|
configurator.AddConsumer<IdentityUserOperationLogConsumer>();
|
||||||
|
configurator.AddEntityFrameworkOutbox<IdentityDbContext>(outbox =>
|
||||||
|
{
|
||||||
|
outbox.UsePostgres();
|
||||||
|
outbox.UseBusOutbox();
|
||||||
|
});
|
||||||
|
configurator.UsingRabbitMq((context, cfg) =>
|
||||||
|
{
|
||||||
|
var virtualHost = string.IsNullOrWhiteSpace(options.VirtualHost) ? "/" : options.VirtualHost.Trim();
|
||||||
|
var virtualHostPath = virtualHost == "/" ? "/" : $"/{virtualHost.TrimStart('/')}";
|
||||||
|
var hostUri = new Uri($"rabbitmq://{options.Host}:{options.Port}{virtualHostPath}");
|
||||||
|
cfg.Host(hostUri, host =>
|
||||||
|
{
|
||||||
|
host.Username(options.Username);
|
||||||
|
host.Password(options.Password);
|
||||||
|
});
|
||||||
|
cfg.PrefetchCount = options.PrefetchCount;
|
||||||
|
cfg.ConfigureEndpoints(context);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// 3. (空行后) 返回服务集合
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using TakeoutSaaS.Shared.Abstractions.Entities;
|
||||||
|
|
||||||
|
namespace TakeoutSaaS.Infrastructure.Logs.Persistence;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 操作日志消息消费去重记录。
|
||||||
|
/// </summary>
|
||||||
|
public sealed class OperationLogInboxMessage : EntityBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 消息唯一标识。
|
||||||
|
/// </summary>
|
||||||
|
public Guid MessageId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 消费时间(UTC)。
|
||||||
|
/// </summary>
|
||||||
|
public DateTime ConsumedAt { get; set; }
|
||||||
|
}
|
||||||
@@ -35,6 +35,11 @@ public sealed class TakeoutLogsDbContext(
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public DbSet<OperationLog> OperationLogs => Set<OperationLog>();
|
public DbSet<OperationLog> OperationLogs => Set<OperationLog>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 操作日志消息去重集合。
|
||||||
|
/// </summary>
|
||||||
|
public DbSet<OperationLogInboxMessage> OperationLogInboxMessages => Set<OperationLogInboxMessage>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 成长值日志集合。
|
/// 成长值日志集合。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -50,6 +55,7 @@ public sealed class TakeoutLogsDbContext(
|
|||||||
ConfigureTenantAuditLog(modelBuilder.Entity<TenantAuditLog>());
|
ConfigureTenantAuditLog(modelBuilder.Entity<TenantAuditLog>());
|
||||||
ConfigureMerchantAuditLog(modelBuilder.Entity<MerchantAuditLog>());
|
ConfigureMerchantAuditLog(modelBuilder.Entity<MerchantAuditLog>());
|
||||||
ConfigureOperationLog(modelBuilder.Entity<OperationLog>());
|
ConfigureOperationLog(modelBuilder.Entity<OperationLog>());
|
||||||
|
ConfigureOperationLogInboxMessage(modelBuilder.Entity<OperationLogInboxMessage>());
|
||||||
ConfigureMemberGrowthLog(modelBuilder.Entity<MemberGrowthLog>());
|
ConfigureMemberGrowthLog(modelBuilder.Entity<MemberGrowthLog>());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,6 +97,15 @@ public sealed class TakeoutLogsDbContext(
|
|||||||
builder.HasIndex(x => x.CreatedAt);
|
builder.HasIndex(x => x.CreatedAt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void ConfigureOperationLogInboxMessage(EntityTypeBuilder<OperationLogInboxMessage> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("operation_log_inbox_messages");
|
||||||
|
builder.HasKey(x => x.Id);
|
||||||
|
builder.Property(x => x.MessageId).IsRequired();
|
||||||
|
builder.Property(x => x.ConsumedAt).IsRequired();
|
||||||
|
builder.HasIndex(x => x.MessageId).IsUnique();
|
||||||
|
}
|
||||||
|
|
||||||
private static void ConfigureMemberGrowthLog(EntityTypeBuilder<MemberGrowthLog> builder)
|
private static void ConfigureMemberGrowthLog(EntityTypeBuilder<MemberGrowthLog> builder)
|
||||||
{
|
{
|
||||||
builder.ToTable("member_growth_logs");
|
builder.ToTable("member_growth_logs");
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using MassTransit;
|
||||||
|
using TakeoutSaaS.Application.Identity.Abstractions;
|
||||||
|
using TakeoutSaaS.Application.Identity.Events;
|
||||||
|
|
||||||
|
namespace TakeoutSaaS.Infrastructure.Logs.Publishers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 身份模块操作日志发布器(基于 MassTransit Outbox)。
|
||||||
|
/// </summary>
|
||||||
|
public sealed class IdentityOperationLogPublisher(IPublishEndpoint publishEndpoint) : IIdentityOperationLogPublisher
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task PublishAsync(IdentityUserOperationLogMessage message, CancellationToken cancellationToken = default)
|
||||||
|
=> publishEndpoint.Publish(message, cancellationToken);
|
||||||
|
}
|
||||||
@@ -0,0 +1,847 @@
|
|||||||
|
// <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("20251227004313_AddIdentityOutbox")]
|
||||||
|
partial class AddIdentityOutbox
|
||||||
|
{
|
||||||
|
/// <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("MassTransit.EntityFrameworkCoreIntegration.OutboxMessage", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("SequenceNumber")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("SequenceNumber"));
|
||||||
|
|
||||||
|
b.Property<string>("Body")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("ContentType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<Guid?>("ConversationId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid?>("CorrelationId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("DestinationAddress")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("EnqueueTime")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ExpirationTime")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("FaultAddress")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<string>("Headers")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid?>("InboxConsumerId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid?>("InboxMessageId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid?>("InitiatorId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid>("MessageId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("MessageType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid?>("OutboxId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("Properties")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid?>("RequestId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("ResponseAddress")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("SentTime")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("SourceAddress")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.HasKey("SequenceNumber");
|
||||||
|
|
||||||
|
b.HasIndex("EnqueueTime");
|
||||||
|
|
||||||
|
b.HasIndex("ExpirationTime");
|
||||||
|
|
||||||
|
b.HasIndex("OutboxId", "SequenceNumber")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("InboxMessageId", "InboxConsumerId", "SequenceNumber")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("OutboxMessage");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.OutboxState", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("OutboxId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Created")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("Delivered")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<long?>("LastSequenceNumber")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<Guid>("LockId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<byte[]>("RowVersion")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.ValueGeneratedOnAddOrUpdate()
|
||||||
|
.HasColumnType("bytea");
|
||||||
|
|
||||||
|
b.HasKey("OutboxId");
|
||||||
|
|
||||||
|
b.HasIndex("Created");
|
||||||
|
|
||||||
|
b.ToTable("OutboxState");
|
||||||
|
});
|
||||||
|
|
||||||
|
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<string>("Email")
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("character varying(128)")
|
||||||
|
.HasComment("邮箱(租户内唯一)。");
|
||||||
|
|
||||||
|
b.Property<int>("FailedLoginCount")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasComment("登录失败次数。");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastLoginAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasComment("最近登录时间(UTC)。");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LockedUntil")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasComment("锁定截止时间(UTC)。");
|
||||||
|
|
||||||
|
b.Property<long?>("MerchantId")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasComment("所属商户(平台管理员为空)。");
|
||||||
|
|
||||||
|
b.Property<bool>("MustChangePassword")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasComment("是否强制修改密码。");
|
||||||
|
|
||||||
|
b.Property<string>("PasswordHash")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)")
|
||||||
|
.HasComment("密码哈希。");
|
||||||
|
|
||||||
|
b.Property<string>("Phone")
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("character varying(32)")
|
||||||
|
.HasComment("手机号(租户内唯一)。");
|
||||||
|
|
||||||
|
b.Property<byte[]>("RowVersion")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAddOrUpdate()
|
||||||
|
.HasColumnType("bytea")
|
||||||
|
.HasComment("并发控制字段。");
|
||||||
|
|
||||||
|
b.Property<int>("Status")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.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.HasIndex("TenantId", "Email")
|
||||||
|
.IsUnique()
|
||||||
|
.HasFilter("\"Email\" IS NOT NULL");
|
||||||
|
|
||||||
|
b.HasIndex("TenantId", "Phone")
|
||||||
|
.IsUnique()
|
||||||
|
.HasFilter("\"Phone\" IS NOT NULL");
|
||||||
|
|
||||||
|
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,101 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace TakeoutSaaS.Infrastructure.Migrations.IdentityDb
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddIdentityOutbox : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "OutboxMessage",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
SequenceNumber = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
EnqueueTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||||
|
SentTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
Headers = table.Column<string>(type: "text", nullable: true),
|
||||||
|
Properties = table.Column<string>(type: "text", nullable: true),
|
||||||
|
InboxMessageId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
InboxConsumerId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
OutboxId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
MessageId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
ContentType = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||||
|
MessageType = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Body = table.Column<string>(type: "text", nullable: false),
|
||||||
|
ConversationId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
CorrelationId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
InitiatorId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
RequestId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
SourceAddress = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||||
|
DestinationAddress = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||||
|
ResponseAddress = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||||
|
FaultAddress = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||||
|
ExpirationTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_OutboxMessage", x => x.SequenceNumber);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "OutboxState",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
OutboxId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
LockId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
RowVersion = table.Column<byte[]>(type: "bytea", rowVersion: true, nullable: true),
|
||||||
|
Created = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
Delivered = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||||
|
LastSequenceNumber = table.Column<long>(type: "bigint", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_OutboxState", x => x.OutboxId);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_OutboxMessage_EnqueueTime",
|
||||||
|
table: "OutboxMessage",
|
||||||
|
column: "EnqueueTime");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_OutboxMessage_ExpirationTime",
|
||||||
|
table: "OutboxMessage",
|
||||||
|
column: "ExpirationTime");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_OutboxMessage_InboxMessageId_InboxConsumerId_SequenceNumber",
|
||||||
|
table: "OutboxMessage",
|
||||||
|
columns: new[] { "InboxMessageId", "InboxConsumerId", "SequenceNumber" },
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_OutboxMessage_OutboxId_SequenceNumber",
|
||||||
|
table: "OutboxMessage",
|
||||||
|
columns: new[] { "OutboxId", "SequenceNumber" },
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_OutboxState_Created",
|
||||||
|
table: "OutboxState",
|
||||||
|
column: "Created");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "OutboxMessage");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "OutboxState");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,127 @@ namespace TakeoutSaaS.Infrastructure.Migrations.IdentityDb
|
|||||||
|
|
||||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.OutboxMessage", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("SequenceNumber")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("SequenceNumber"));
|
||||||
|
|
||||||
|
b.Property<string>("Body")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("ContentType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<Guid?>("ConversationId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid?>("CorrelationId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("DestinationAddress")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("EnqueueTime")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ExpirationTime")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("FaultAddress")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<string>("Headers")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid?>("InboxConsumerId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid?>("InboxMessageId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid?>("InitiatorId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid>("MessageId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("MessageType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid?>("OutboxId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("Properties")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid?>("RequestId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("ResponseAddress")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("SentTime")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("SourceAddress")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.HasKey("SequenceNumber");
|
||||||
|
|
||||||
|
b.HasIndex("EnqueueTime");
|
||||||
|
|
||||||
|
b.HasIndex("ExpirationTime");
|
||||||
|
|
||||||
|
b.HasIndex("OutboxId", "SequenceNumber")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("InboxMessageId", "InboxConsumerId", "SequenceNumber")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("OutboxMessage");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.OutboxState", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("OutboxId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Created")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("Delivered")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<long?>("LastSequenceNumber")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<Guid>("LockId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<byte[]>("RowVersion")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.ValueGeneratedOnAddOrUpdate()
|
||||||
|
.HasColumnType("bytea");
|
||||||
|
|
||||||
|
b.HasKey("OutboxId");
|
||||||
|
|
||||||
|
b.HasIndex("Created");
|
||||||
|
|
||||||
|
b.ToTable("OutboxState");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.IdentityUser", b =>
|
modelBuilder.Entity("TakeoutSaaS.Domain.Identity.Entities.IdentityUser", b =>
|
||||||
{
|
{
|
||||||
b.Property<long>("Id")
|
b.Property<long>("Id")
|
||||||
|
|||||||
@@ -0,0 +1,358 @@
|
|||||||
|
// <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.Logs.Persistence;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace TakeoutSaaS.Infrastructure.Migrations.LogsDb
|
||||||
|
{
|
||||||
|
[DbContext(typeof(TakeoutLogsDbContext))]
|
||||||
|
[Migration("20251227004337_AddOperationLogInboxMessages")]
|
||||||
|
partial class AddOperationLogInboxMessages
|
||||||
|
{
|
||||||
|
/// <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.Membership.Entities.MemberGrowthLog", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasComment("实体唯一标识。");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<int>("ChangeValue")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasComment("变动数量。");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasComment("创建时间(UTC)。");
|
||||||
|
|
||||||
|
b.Property<long?>("CreatedBy")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
|
||||||
|
|
||||||
|
b.Property<int>("CurrentValue")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasComment("当前成长值。");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DeletedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasComment("软删除时间(UTC),未删除时为 null。");
|
||||||
|
|
||||||
|
b.Property<long?>("DeletedBy")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasComment("删除人用户标识(软删除),未删除时为 null。");
|
||||||
|
|
||||||
|
b.Property<long>("MemberId")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasComment("会员标识。");
|
||||||
|
|
||||||
|
b.Property<string>("Notes")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)")
|
||||||
|
.HasComment("备注。");
|
||||||
|
|
||||||
|
b.Property<DateTime>("OccurredAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.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", "MemberId", "OccurredAt");
|
||||||
|
|
||||||
|
b.ToTable("member_growth_logs", null, t =>
|
||||||
|
{
|
||||||
|
t.HasComment("成长值变动日志。");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("TakeoutSaaS.Domain.Merchants.Entities.MerchantAuditLog", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasComment("实体唯一标识。");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<int>("Action")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.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(1024)
|
||||||
|
.HasColumnType("character varying(1024)")
|
||||||
|
.HasComment("详情描述。");
|
||||||
|
|
||||||
|
b.Property<long>("MerchantId")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasComment("商户标识。");
|
||||||
|
|
||||||
|
b.Property<long?>("OperatorId")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasComment("操作人 ID。");
|
||||||
|
|
||||||
|
b.Property<string>("OperatorName")
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("character varying(64)")
|
||||||
|
.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", "MerchantId");
|
||||||
|
|
||||||
|
b.ToTable("merchant_audit_logs", null, t =>
|
||||||
|
{
|
||||||
|
t.HasComment("商户入驻审核日志。");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("TakeoutSaaS.Domain.Tenants.Entities.OperationLog", 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>("OperationType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("character varying(64)")
|
||||||
|
.HasComment("操作类型:BatchExtend, BatchRemind, StatusChange 等。");
|
||||||
|
|
||||||
|
b.Property<string>("OperatorId")
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("character varying(64)")
|
||||||
|
.HasComment("操作人ID。");
|
||||||
|
|
||||||
|
b.Property<string>("OperatorName")
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("character varying(128)")
|
||||||
|
.HasComment("操作人名称。");
|
||||||
|
|
||||||
|
b.Property<string>("Parameters")
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasComment("操作参数(JSON)。");
|
||||||
|
|
||||||
|
b.Property<string>("Result")
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasComment("操作结果(JSON)。");
|
||||||
|
|
||||||
|
b.Property<bool>("Success")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasComment("是否成功。");
|
||||||
|
|
||||||
|
b.Property<string>("TargetIds")
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasComment("目标ID列表(JSON)。");
|
||||||
|
|
||||||
|
b.Property<string>("TargetType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("character varying(64)")
|
||||||
|
.HasComment("目标类型:Subscription, Bill 等。");
|
||||||
|
|
||||||
|
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("CreatedAt");
|
||||||
|
|
||||||
|
b.HasIndex("OperationType", "CreatedAt");
|
||||||
|
|
||||||
|
b.ToTable("operation_logs", null, t =>
|
||||||
|
{
|
||||||
|
t.HasComment("运营操作日志。");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("TakeoutSaaS.Domain.Tenants.Entities.TenantAuditLog", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasComment("实体唯一标识。");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<int>("Action")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasComment("操作类型。");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasComment("创建时间(UTC)。");
|
||||||
|
|
||||||
|
b.Property<long?>("CreatedBy")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasComment("创建人用户标识,匿名或系统操作时为 null。");
|
||||||
|
|
||||||
|
b.Property<int?>("CurrentStatus")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasComment("新状态。");
|
||||||
|
|
||||||
|
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(1024)
|
||||||
|
.HasColumnType("character varying(1024)")
|
||||||
|
.HasComment("详细描述。");
|
||||||
|
|
||||||
|
b.Property<long?>("OperatorId")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasComment("操作人 ID。");
|
||||||
|
|
||||||
|
b.Property<string>("OperatorName")
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("character varying(64)")
|
||||||
|
.HasComment("操作人名称。");
|
||||||
|
|
||||||
|
b.Property<int?>("PreviousStatus")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasComment("原状态。");
|
||||||
|
|
||||||
|
b.Property<long>("TenantId")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasComment("关联的租户标识。");
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
b.ToTable("tenant_audit_logs", null, t =>
|
||||||
|
{
|
||||||
|
t.HasComment("租户运营审核日志。");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("TakeoutSaaS.Infrastructure.Logs.Persistence.OperationLogInboxMessage", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasComment("实体唯一标识。");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("ConsumedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<Guid>("MessageId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("MessageId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("operation_log_inbox_messages", (string)null);
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace TakeoutSaaS.Infrastructure.Migrations.LogsDb
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddOperationLogInboxMessages : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "operation_log_inbox_messages",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false, comment: "实体唯一标识。")
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
MessageId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
ConsumedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_operation_log_inbox_messages", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_operation_log_inbox_messages_MessageId",
|
||||||
|
table: "operation_log_inbox_messages",
|
||||||
|
column: "MessageId",
|
||||||
|
unique: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "operation_log_inbox_messages");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -326,6 +326,29 @@ namespace TakeoutSaaS.Infrastructure.Migrations.LogsDb
|
|||||||
t.HasComment("租户运营审核日志。");
|
t.HasComment("租户运营审核日志。");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("TakeoutSaaS.Infrastructure.Logs.Persistence.OperationLogInboxMessage", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasComment("实体唯一标识。");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("ConsumedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<Guid>("MessageId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("MessageId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("operation_log_inbox_messages", (string)null);
|
||||||
|
});
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user