From d2c7e1fb7185a6c667e98c29147a750a7e41b0d4 Mon Sep 17 00:00:00 2001 From: MSuMshk <2039814060@qq.com> Date: Mon, 15 Dec 2025 15:15:42 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E4=BC=AA=E8=A3=85?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E4=B8=8E=E9=87=8D=E7=BD=AE=E9=93=BE=E6=8E=A5?= =?UTF-8?q?=E8=BF=94=E5=9B=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/TenantsController.cs | 2 +- .../ImpersonateTenantCommandHandler.cs | 63 +++++++++++-------- 2 files changed, 39 insertions(+), 26 deletions(-) diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/TenantsController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/TenantsController.cs index 0e4346f..80b2575 100644 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/TenantsController.cs +++ b/src/Api/TakeoutSaaS.AdminApi/Controllers/TenantsController.cs @@ -359,7 +359,7 @@ public sealed class TenantsController(IMediator mediator) : BaseApiController var resetUrl = $"{origin}/#/auth/reset-password?token={Uri.EscapeDataString(token)}"; // 3. (空行后) 返回链接 - return ApiResponse.Ok(resetUrl); + return ApiResponse.Ok(data: resetUrl); } /// diff --git a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/ImpersonateTenantCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/ImpersonateTenantCommandHandler.cs index 8f80077..68ce3b2 100644 --- a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/ImpersonateTenantCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/ImpersonateTenantCommandHandler.cs @@ -18,6 +18,7 @@ namespace TakeoutSaaS.Application.App.Tenants.Handlers; public sealed class ImpersonateTenantCommandHandler( ITenantRepository tenantRepository, ITenantProvider tenantProvider, + ITenantContextAccessor tenantContextAccessor, ICurrentUserAccessor currentUserAccessor, IAdminAuthService adminAuthService, IJwtTokenService jwtTokenService) @@ -35,6 +36,12 @@ public sealed class ImpersonateTenantCommandHandler( throw new BusinessException(ErrorCodes.Forbidden, "仅平台超级管理员可执行伪装登录"); } + // 2. (空行后) 读取操作者信息(在平台租户上下文内) + var operatorProfile = await adminAuthService.GetProfileAsync(currentUserAccessor.UserId, cancellationToken); + var operatorName = string.IsNullOrWhiteSpace(operatorProfile.DisplayName) + ? $"user:{currentUserAccessor.UserId}" + : operatorProfile.DisplayName; + // 2. (空行后) 校验租户存在且存在主管理员 var tenant = await tenantRepository.FindByIdAsync(request.TenantId, cancellationToken) ?? throw new BusinessException(ErrorCodes.NotFound, "租户不存在"); @@ -44,32 +51,38 @@ public sealed class ImpersonateTenantCommandHandler( throw new BusinessException(ErrorCodes.BadRequest, "该租户未配置主管理员账号,无法伪装登录"); } - // 3. (空行后) 为租户主管理员签发令牌 - var targetProfile = await adminAuthService.GetProfileAsync(tenant.PrimaryOwnerUserId.Value, cancellationToken); - var token = await jwtTokenService.CreateTokensAsync(targetProfile, false, 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 + // 3. (空行后) 进入目标租户上下文以读取租户内用户(避免多租户查询过滤导致找不到用户) + var originalTenantContext = tenantContextAccessor.Current; + tenantContextAccessor.Current = new TenantContext(tenant.Id, null, "admin:impersonate"); + try { - TenantId = tenant.Id, - Action = TenantAuditAction.ImpersonatedLogin, - Title = "伪装登录", - Description = $"操作者:{operatorName},目标账号:{targetProfile.Account}", - OperatorId = currentUserAccessor.UserId, - OperatorName = operatorName, - PreviousStatus = tenant.Status, - CurrentStatus = tenant.Status - }; - await tenantRepository.AddAuditLogAsync(auditLog, cancellationToken); - await tenantRepository.SaveChangesAsync(cancellationToken); + // 4. 为租户主管理员签发令牌 + var targetProfile = await adminAuthService.GetProfileAsync(tenant.PrimaryOwnerUserId.Value, cancellationToken); + var token = await jwtTokenService.CreateTokensAsync(targetProfile, false, cancellationToken); - // 5. (空行后) 返回令牌 - return token; + // 5. (空行后) 恢复租户上下文后写入审计日志 + tenantContextAccessor.Current = originalTenantContext; + var auditLog = new TenantAuditLog + { + TenantId = tenant.Id, + Action = TenantAuditAction.ImpersonatedLogin, + Title = "伪装登录", + Description = $"操作者:{operatorName},目标账号:{targetProfile.Account}", + OperatorId = currentUserAccessor.UserId, + OperatorName = operatorName, + PreviousStatus = tenant.Status, + CurrentStatus = tenant.Status + }; + await tenantRepository.AddAuditLogAsync(auditLog, cancellationToken); + await tenantRepository.SaveChangesAsync(cancellationToken); + + // 6. (空行后) 返回令牌 + return token; + } + finally + { + // 7. (空行后) 确保恢复租户上下文 + tenantContextAccessor.Current = originalTenantContext; + } } } -