refactor: 精简手动创建租户命令字段

- 移除冗余字段:CoverImageUrl、Website、SuspendedAt、SuspensionReason
- 移除冗余字段:ScheduledPackageId、AdditionalDataJson、ReviewedByName、ReviewRemarks、AdminMerchantId
- 更新Handler移除对应字段的处理逻辑

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
MSuMshk
2026-02-02 21:43:22 +08:00
parent a995d3c64d
commit e92b333076
2 changed files with 21 additions and 98 deletions

View File

@@ -42,16 +42,6 @@ public sealed record CreateTenantManuallyCommand : IRequest<TenantDetailDto>
/// </summary>
public string? LogoUrl { get; init; }
/// <summary>
/// 封面图地址。
/// </summary>
public string? CoverImageUrl { get; init; }
/// <summary>
/// 官网地址。
/// </summary>
public string? Website { get; init; }
/// <summary>
/// 国家/地区。
/// </summary>
@@ -97,16 +87,6 @@ public sealed record CreateTenantManuallyCommand : IRequest<TenantDetailDto>
/// </summary>
public string? Remarks { get; init; }
/// <summary>
/// 暂停时间。
/// </summary>
public DateTime? SuspendedAt { get; init; }
/// <summary>
/// 暂停原因。
/// </summary>
public string? SuspensionReason { get; init; }
/// <summary>
/// 租户状态。
/// </summary>
@@ -153,11 +133,6 @@ public sealed record CreateTenantManuallyCommand : IRequest<TenantDetailDto>
/// </summary>
public SubscriptionStatus SubscriptionStatus { get; init; } = SubscriptionStatus.Active;
/// <summary>
/// 预约变更的套餐 ID。
/// </summary>
public string? ScheduledPackageId { get; init; }
/// <summary>
/// 订阅备注。
/// </summary>
@@ -215,21 +190,6 @@ public sealed record CreateTenantManuallyCommand : IRequest<TenantDetailDto>
/// </summary>
public string? BankName { get; init; }
/// <summary>
/// 附加数据 JSON。
/// </summary>
public string? AdditionalDataJson { get; init; }
/// <summary>
/// 审核人姓名。
/// </summary>
public string? ReviewedByName { get; init; }
/// <summary>
/// 审核备注。
/// </summary>
public string? ReviewRemarks { get; init; }
// 4. 管理员账号identity.identity_users
/// <summary>
@@ -252,11 +212,6 @@ public sealed record CreateTenantManuallyCommand : IRequest<TenantDetailDto>
/// </summary>
public string? AdminAvatar { get; init; }
/// <summary>
/// 管理员所属商户 ID。
/// </summary>
public string? AdminMerchantId { get; init; }
// 5. 账单信息public.tenant_billing_statements
/// <summary>

View File

@@ -54,58 +54,34 @@ public sealed class CreateTenantManuallyCommandHandler(
throw new BusinessException(ErrorCodes.BadRequest, "套餐 ID 无效");
}
// 3. 解析可选的预约套餐 ID
long? scheduledPackageId = null;
if (!string.IsNullOrWhiteSpace(request.ScheduledPackageId))
{
if (!long.TryParse(request.ScheduledPackageId, out var parsedScheduledId) || parsedScheduledId <= 0)
{
throw new BusinessException(ErrorCodes.BadRequest, "预约套餐 ID 无效");
}
scheduledPackageId = parsedScheduledId;
}
// 4. 解析可选的管理员商户 ID
long? adminMerchantId = null;
if (!string.IsNullOrWhiteSpace(request.AdminMerchantId))
{
if (!long.TryParse(request.AdminMerchantId, out var parsedMerchantId) || parsedMerchantId <= 0)
{
throw new BusinessException(ErrorCodes.BadRequest, "管理员商户 ID 无效");
}
adminMerchantId = parsedMerchantId;
}
// 5. 校验租户编码唯一性
// 3. 校验租户编码唯一性
if (await tenantRepository.ExistsByCodeAsync(code, null, cancellationToken))
{
throw new BusinessException(ErrorCodes.Conflict, "租户编码已存在");
}
// 6. 校验套餐存在性
// 4. 校验套餐存在性
var package = await tenantPackageRepository.GetByIdAsync(tenantPackageId, cancellationToken);
if (package is null)
{
throw new BusinessException(ErrorCodes.NotFound, "套餐不存在");
}
// 7. 生成租户 ID
// 5. 生成租户 ID
var tenantId = idGenerator.NextId();
// 8. 校验管理员账号唯一性(租户内)
// 6. 校验管理员账号唯一性(租户内)
if (await identityUserRepository.ExistsByAccountAsync(PortalType.Tenant, tenantId, adminAccount, null, cancellationToken))
{
throw new BusinessException(ErrorCodes.Conflict, "管理员账号已存在");
}
// 9. 计算订阅时间
// 7. 计算订阅时间
var effectiveFrom = request.SubscriptionEffectiveFrom ?? DateTime.UtcNow;
var effectiveTo = effectiveFrom.AddMonths(request.DurationMonths);
var nextBillingDate = request.NextBillingDate ?? effectiveTo;
// 10. 创建管理员账号实体
// 8. 创建管理员账号实体
var adminUser = new IdentityUser
{
Id = idGenerator.NextId(),
@@ -121,23 +97,23 @@ public sealed class CreateTenantManuallyCommandHandler(
LockedUntil = null,
LastLoginAt = null,
MustChangePassword = false,
MerchantId = adminMerchantId,
MerchantId = null,
Avatar = request.AdminAvatar?.Trim()
};
adminUser.PasswordHash = passwordHasher.HashPassword(adminUser, request.AdminPassword);
// 11. 【Saga 步骤 1】先在 Identity 库创建管理员账号
// 9. 【Saga 步骤 1】先在 Identity 库创建管理员账号
await identityUserRepository.AddAsync(adminUser, cancellationToken);
await identityUserRepository.SaveChangesAsync(cancellationToken);
logger.LogInformation("租户 {TenantId} 管理员账号 {AdminUserId} 创建成功", tenantId, adminUser.Id);
try
{
// 12. 根据是否跳过审核确定租户状态和认证状态
// 10. 根据是否跳过审核确定租户状态和认证状态
var tenantStatus = request.IsSkipApproval ? TenantStatus.Active : TenantStatus.PendingReview;
var verificationStatus = request.IsSkipApproval ? TenantVerificationStatus.Approved : TenantVerificationStatus.Pending;
// 13. 创建租户实体
// 11. 创建租户实体
var tenant = new Tenant
{
Id = tenantId,
@@ -147,8 +123,6 @@ public sealed class CreateTenantManuallyCommandHandler(
LegalEntityName = request.LegalEntityName?.Trim(),
Industry = request.Industry?.Trim(),
LogoUrl = request.LogoUrl?.Trim(),
CoverImageUrl = request.CoverImageUrl?.Trim(),
Website = request.Website?.Trim(),
Country = request.Country?.Trim(),
Province = request.Province?.Trim(),
City = request.City?.Trim(),
@@ -159,14 +133,12 @@ public sealed class CreateTenantManuallyCommandHandler(
Tags = request.Tags?.Trim(),
Remarks = request.Remarks?.Trim(),
Status = tenantStatus,
SuspendedAt = request.SuspendedAt,
SuspensionReason = request.SuspensionReason?.Trim(),
EffectiveFrom = effectiveFrom,
EffectiveTo = effectiveTo,
PrimaryOwnerUserId = adminUser.Id
};
// 14. 创建订阅实体
// 12. 创建订阅实体
var subscription = new TenantSubscription
{
Id = idGenerator.NextId(),
@@ -177,11 +149,10 @@ public sealed class CreateTenantManuallyCommandHandler(
EffectiveTo = effectiveTo,
NextBillingDate = nextBillingDate,
AutoRenew = request.AutoRenew,
ScheduledPackageId = scheduledPackageId,
Notes = request.SubscriptionNotes?.Trim()
};
// 15. 创建认证资料实体
// 13. 创建认证资料实体
var verification = new TenantVerificationProfile
{
Id = idGenerator.NextId(),
@@ -196,18 +167,15 @@ public sealed class CreateTenantManuallyCommandHandler(
BankAccountName = request.BankAccountName?.Trim(),
BankAccountNumber = request.BankAccountNumber?.Trim(),
BankName = request.BankName?.Trim(),
AdditionalDataJson = request.AdditionalDataJson?.Trim(),
SubmittedAt = DateTime.UtcNow,
ReviewedAt = request.IsSkipApproval ? DateTime.UtcNow : null,
ReviewedBy = request.IsSkipApproval ? currentUserAccessor.UserId : null,
ReviewedByName = request.IsSkipApproval ? request.ReviewedByName?.Trim() : null,
ReviewRemarks = request.IsSkipApproval ? request.ReviewRemarks?.Trim() : null
ReviewedBy = request.IsSkipApproval ? currentUserAccessor.UserId : null
};
// 16. 根据套餐配额创建配额使用记录
// 14. 根据套餐配额创建配额使用记录
var quotaUsages = CreateQuotaUsagesFromPackage(tenantId, package);
// 17. 创建账单记录和支付记录(可选)
// 15. 创建账单记录和支付记录(可选)
TenantBillingStatement? billing = null;
TenantPayment? payment = null;
if (request.CreateBilling)
@@ -225,20 +193,20 @@ public sealed class CreateTenantManuallyCommandHandler(
request.BillingStatus,
request.BillingNotes);
// 17. 如果账单状态为已支付,创建支付记录
// 16. 如果账单状态为已支付,创建支付记录
if (request.BillingStatus == TenantBillingStatus.Paid && billing.AmountDue > 0)
{
payment = CreatePaymentFromBilling(tenantId, billing);
}
}
// 18. 【Saga 步骤 2】在 App 库创建租户、订阅、认证资料、配额使用记录、账单、支付记录(使用事务)
// 17. 【Saga 步骤 2】在 App 库创建租户、订阅、认证资料、配额使用记录、账单、支付记录(使用事务)
await tenantRepository.CreateTenantWithRelatedDataAsync(tenant, subscription, verification, quotaUsages, billing, payment, cancellationToken);
logger.LogInformation("租户 {TenantId} 及相关数据创建成功,跳过审核:{IsSkipApproval}", tenantId, request.IsSkipApproval);
}
catch (Exception ex)
{
// 16. 【Saga 补偿】App 库操作失败,回滚 Identity 库的管理员账号
// 18. 【Saga 补偿】App 库操作失败,回滚 Identity 库的管理员账号
// 记录完整异常信息(包括内部异常)
var fullErrorMessage = ex.InnerException?.Message ?? ex.Message;
logger.LogError(ex, "租户 {TenantId} 创建失败,错误详情:{ErrorDetail},开始回滚管理员账号 {AdminUserId}",
@@ -252,7 +220,7 @@ public sealed class CreateTenantManuallyCommandHandler(
}
catch (Exception rollbackEx)
{
// 17. 补偿失败,记录严重错误(需要人工介入)
// 19. 补偿失败,记录严重错误(需要人工介入)
logger.LogCritical(
rollbackEx,
"严重:租户 {TenantId} 创建失败且管理员账号 {AdminUserId} 回滚失败,需要人工介入清理",
@@ -260,11 +228,11 @@ public sealed class CreateTenantManuallyCommandHandler(
adminUser.Id);
}
// 18. 重新抛出业务异常(包含详细错误信息)
// 20. 重新抛出业务异常(包含详细错误信息)
throw new BusinessException(ErrorCodes.InternalServerError, $"创建租户失败:{fullErrorMessage}");
}
// 19. 查询并返回租户详情
// 21. 查询并返回租户详情
var detail = await mediator.Send(new GetTenantDetailQuery { TenantId = tenantId }, cancellationToken);
return detail ?? throw new BusinessException(ErrorCodes.InternalServerError, "创建租户成功但查询详情失败");
}