using MediatR; using TakeoutSaaS.Application.App.Tenants.Commands; using TakeoutSaaS.Application.Identity.Abstractions; using TakeoutSaaS.Domain.Identity.Repositories; using TakeoutSaaS.Domain.Tenants.Entities; using TakeoutSaaS.Domain.Tenants.Enums; using TakeoutSaaS.Domain.Tenants.Repositories; using TakeoutSaaS.Shared.Abstractions.Constants; using TakeoutSaaS.Shared.Abstractions.Exceptions; using TakeoutSaaS.Shared.Abstractions.Security; using TakeoutSaaS.Shared.Abstractions.Tenancy; namespace TakeoutSaaS.Application.App.Tenants.Handlers; /// /// 生成租户主管理员重置链接令牌处理器(平台超级管理员使用)。 /// public sealed class CreateTenantAdminResetLinkTokenCommandHandler( ITenantRepository tenantRepository, ITenantProvider tenantProvider, ITenantContextAccessor tenantContextAccessor, IIdentityUserRepository identityUserRepository, ICurrentUserAccessor currentUserAccessor, IAdminAuthService adminAuthService, IAdminPasswordResetTokenStore tokenStore) : IRequestHandler { private const long PlatformRootTenantId = 1000000000001; /// public async Task Handle(CreateTenantAdminResetLinkTokenCommand request, CancellationToken cancellationToken) { // 1. 校验仅允许平台超级管理员执行 var currentTenantId = tenantProvider.GetCurrentTenantId(); if (currentTenantId != PlatformRootTenantId) { throw new BusinessException(ErrorCodes.Forbidden, "仅平台超级管理员可生成重置链接"); } // 2. 校验租户存在且存在主管理员 var tenant = await tenantRepository.FindByIdAsync(request.TenantId, cancellationToken) ?? throw new BusinessException(ErrorCodes.NotFound, "租户不存在"); // 2.1 若缺少主管理员则自动回填(兼容历史数据) if (!tenant.PrimaryOwnerUserId.HasValue || tenant.PrimaryOwnerUserId.Value == 0) { var originalContextForFix = tenantContextAccessor.Current; tenantContextAccessor.Current = new TenantContext(tenant.Id, tenant.Code, "admin:reset-link:fix-owner"); try { var users = await identityUserRepository.SearchAsync(tenant.Id, keyword: null, cancellationToken); var ownerCandidate = users.OrderBy(x => x.CreatedAt).FirstOrDefault(); if (ownerCandidate == null) { throw new BusinessException(ErrorCodes.BadRequest, "该租户未配置主管理员账号,且未找到可用管理员账号"); } tenant.PrimaryOwnerUserId = ownerCandidate.Id; await tenantRepository.UpdateTenantAsync(tenant, cancellationToken); await tenantRepository.SaveChangesAsync(cancellationToken); } finally { tenantContextAccessor.Current = originalContextForFix; } } // 3. 签发一次性重置令牌(默认 24 小时有效) var token = await tokenStore.IssueAsync(tenant.PrimaryOwnerUserId.Value, DateTime.UtcNow.AddHours(24), cancellationToken); // 4. 写入审计日志 var operatorProfile = await adminAuthService.GetProfileAsync(currentUserAccessor.UserId, cancellationToken); var operatorName = string.IsNullOrWhiteSpace(operatorProfile.DisplayName) ? $"user:{currentUserAccessor.UserId}" : operatorProfile.DisplayName; var auditLog = new TenantAuditLog { TenantId = tenant.Id, Action = TenantAuditAction.AdminResetLinkIssued, Title = "生成重置链接", Description = $"操作者:{operatorName},目标用户ID:{tenant.PrimaryOwnerUserId.Value}", OperatorId = currentUserAccessor.UserId, OperatorName = operatorName, PreviousStatus = tenant.Status, CurrentStatus = tenant.Status }; await tenantRepository.AddAuditLogAsync(auditLog, cancellationToken); await tenantRepository.SaveChangesAsync(cancellationToken); // 5. 返回令牌 return token; } }