feat: 租户创建时自动创建商户和证照记录
- 扩展 MerchantDocumentType 枚举,新增法人身份证、卫生许可证等类型 - 新增 TenantCreatedEvent 领域事件 - 修改 CreateTenantManuallyCommandHandler 发布租户创建事件 - 新增 TenantCreatedEventConsumer 消费者,自动创建商户和证照 - 实现 Tenant 1:1 Merchant 的业务关系 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
using MassTransit;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using TakeoutSaaS.Domain.Merchants.Entities;
|
||||
using TakeoutSaaS.Domain.Merchants.Enums;
|
||||
using TakeoutSaaS.Domain.Merchants.Repositories;
|
||||
using TakeoutSaaS.Domain.Tenants.Events;
|
||||
using TakeoutSaaS.Shared.Abstractions.Ids;
|
||||
|
||||
namespace TakeoutSaaS.Infrastructure.App.Consumers;
|
||||
|
||||
/// <summary>
|
||||
/// 租户创建事件消费者。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 当租户创建成功后,自动创建对应的商户和证照记录。
|
||||
/// 实现 Tenant 1:1 Merchant 的业务关系。
|
||||
/// </remarks>
|
||||
public sealed class TenantCreatedEventConsumer(
|
||||
IMerchantRepository merchantRepository,
|
||||
IIdGenerator idGenerator,
|
||||
ILogger<TenantCreatedEventConsumer> logger) : IConsumer<TenantCreatedEvent>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task Consume(ConsumeContext<TenantCreatedEvent> context)
|
||||
{
|
||||
var message = context.Message;
|
||||
var tenantId = message.TenantId;
|
||||
|
||||
// 1. 幂等检查:是否已存在该租户的商户
|
||||
var existingMerchant = await merchantRepository.FindByTenantIdAsync(tenantId, context.CancellationToken);
|
||||
if (existingMerchant is not null)
|
||||
{
|
||||
logger.LogInformation("租户 {TenantId} 的商户已存在(商户 ID:{MerchantId}),跳过创建", tenantId, existingMerchant.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 创建商户实体
|
||||
var merchantId = idGenerator.NextId();
|
||||
var merchantStatus = message.IsSkipApproval ? MerchantStatus.Approved : MerchantStatus.Pending;
|
||||
var documentStatus = message.IsSkipApproval ? MerchantDocumentStatus.Approved : MerchantDocumentStatus.Pending;
|
||||
|
||||
var merchant = new Merchant
|
||||
{
|
||||
Id = merchantId,
|
||||
TenantId = tenantId,
|
||||
BrandName = message.Name,
|
||||
BrandAlias = message.ShortName,
|
||||
LogoUrl = message.LogoUrl,
|
||||
BusinessLicenseNumber = message.BusinessLicenseNumber,
|
||||
BusinessLicenseImageUrl = message.BusinessLicenseUrl,
|
||||
LegalPerson = message.LegalPersonName,
|
||||
ContactPhone = message.ContactPhone ?? string.Empty,
|
||||
ContactEmail = message.ContactEmail,
|
||||
Province = message.Province,
|
||||
City = message.City,
|
||||
Address = message.Address,
|
||||
Status = merchantStatus,
|
||||
JoinedAt = message.IsSkipApproval ? DateTime.UtcNow : null,
|
||||
ApprovedAt = message.IsSkipApproval ? DateTime.UtcNow : null,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
await merchantRepository.AddMerchantAsync(merchant, context.CancellationToken);
|
||||
logger.LogInformation("租户 {TenantId} 的商户 {MerchantId} 创建成功", tenantId, merchantId);
|
||||
|
||||
// 3. 创建证照记录(营业执照)
|
||||
if (!string.IsNullOrWhiteSpace(message.BusinessLicenseUrl))
|
||||
{
|
||||
var businessLicenseDoc = new MerchantDocument
|
||||
{
|
||||
Id = idGenerator.NextId(),
|
||||
TenantId = tenantId,
|
||||
MerchantId = merchantId,
|
||||
DocumentType = MerchantDocumentType.BusinessLicense,
|
||||
Status = documentStatus,
|
||||
FileUrl = message.BusinessLicenseUrl,
|
||||
DocumentNumber = message.BusinessLicenseNumber,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
await merchantRepository.AddDocumentAsync(businessLicenseDoc, context.CancellationToken);
|
||||
logger.LogInformation("商户 {MerchantId} 营业执照证照记录创建成功", merchantId);
|
||||
}
|
||||
|
||||
// 4. 创建证照记录(法人身份证正面)
|
||||
if (!string.IsNullOrWhiteSpace(message.LegalPersonIdFrontUrl))
|
||||
{
|
||||
var idFrontDoc = new MerchantDocument
|
||||
{
|
||||
Id = idGenerator.NextId(),
|
||||
TenantId = tenantId,
|
||||
MerchantId = merchantId,
|
||||
DocumentType = MerchantDocumentType.LegalPersonIdFront,
|
||||
Status = documentStatus,
|
||||
FileUrl = message.LegalPersonIdFrontUrl,
|
||||
DocumentNumber = message.LegalPersonIdNumber,
|
||||
Remarks = message.LegalPersonName,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
await merchantRepository.AddDocumentAsync(idFrontDoc, context.CancellationToken);
|
||||
logger.LogInformation("商户 {MerchantId} 法人身份证正面证照记录创建成功", merchantId);
|
||||
}
|
||||
|
||||
// 5. 创建证照记录(法人身份证背面)
|
||||
if (!string.IsNullOrWhiteSpace(message.LegalPersonIdBackUrl))
|
||||
{
|
||||
var idBackDoc = new MerchantDocument
|
||||
{
|
||||
Id = idGenerator.NextId(),
|
||||
TenantId = tenantId,
|
||||
MerchantId = merchantId,
|
||||
DocumentType = MerchantDocumentType.LegalPersonIdBack,
|
||||
Status = documentStatus,
|
||||
FileUrl = message.LegalPersonIdBackUrl,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
await merchantRepository.AddDocumentAsync(idBackDoc, context.CancellationToken);
|
||||
logger.LogInformation("商户 {MerchantId} 法人身份证背面证照记录创建成功", merchantId);
|
||||
}
|
||||
|
||||
// 6. 持久化所有变更
|
||||
await merchantRepository.SaveChangesAsync(context.CancellationToken);
|
||||
logger.LogInformation("租户 {TenantId} 的商户 {MerchantId} 及证照记录全部创建完成", tenantId, merchantId);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using MassTransit;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TakeoutSaaS.Infrastructure.App.Consumers;
|
||||
using TakeoutSaaS.Infrastructure.Identity.Persistence;
|
||||
using TakeoutSaaS.Infrastructure.Logs.Consumers;
|
||||
using TakeoutSaaS.Module.Messaging.Options;
|
||||
@@ -31,6 +32,7 @@ public static class OperationLogOutboxServiceCollectionExtensions
|
||||
services.AddMassTransit(configurator =>
|
||||
{
|
||||
configurator.AddConsumer<IdentityUserOperationLogConsumer>();
|
||||
configurator.AddConsumer<TenantCreatedEventConsumer>();
|
||||
configurator.AddEntityFrameworkOutbox<IdentityDbContext>(outbox =>
|
||||
{
|
||||
outbox.UsePostgres();
|
||||
|
||||
Reference in New Issue
Block a user