using Dpz.Core.Public.Entity.Auth;
using Dpz.Core.Service.Mediator.Features.Auth.Contracts;

namespace Dpz.Core.Service.Mediator.Features.Auth.Queries;

public class SearchAuthTokensQueryHandler(
    IRepository<DpzToken> tokenRepository,
    IRepository<DpzApplication> applicationRepository
) : IRequestHandler<SearchAuthTokensQuery, PagedListWarp<AuthTokenPageItem>>
{
    public async ValueTask<PagedListWarp<AuthTokenPageItem>> Handle(
        SearchAuthTokensQuery request,
        CancellationToken cancellationToken
    )
    {
        var query = tokenRepository.SearchFor(x => true);

        if (!string.IsNullOrWhiteSpace(request.UserId))
        {
            query = query.Where(x => x.Subject == request.UserId);
        }

        if (
            !string.IsNullOrWhiteSpace(request.ClientId)
            && ObjectId.TryParse(request.ClientId, out var appId)
        )
        {
            query = query.Where(x => x.ApplicationId == appId);
        }

        var pagedList = await query
            .OrderByDescending(x => x.Id)
            .ToPagedListAsync(request.Page, request.Limit, cancellationToken);

        var applicationIds = pagedList.Select(x => x.ApplicationId).ToHashSet();
        var applications = await applicationRepository
            .SearchFor(Builders<DpzApplication>.Filter.In(x => x.Id, applicationIds))
            .ToListAsync(cancellationToken);

        var items = pagedList
            .Select(x =>
            {
                var app = applications.FirstOrDefault(y => y.Id == x.ApplicationId);
                return new AuthTokenPageItem
                {
                    Id = x.Id.ToString(),
                    ApplicationId = x.ApplicationId.ToString(),
                    ApplicationName = app?.DisplayName,
                    AuthorizationId = x.AuthorizationId.ToString(),
                    ConcurrencyToken = x.ConcurrencyToken,
                    CreationDate = x.CreationDate,
                    ExpirationDate = x.ExpirationDate,
                    Payload = x.Payload,
                    Properties = x.Properties,
                    RedemptionDate = x.RedemptionDate,
                    ReferenceId = x.ReferenceId,
                    Status = x.Status,
                    Subject = x.Subject,
                    Type = x.Type,
                };
            })
            .ToList();

        return new PagedListWarp<AuthTokenPageItem>(
            pagedList.CurrentPageIndex,
            pagedList.PageSize,
            pagedList.TotalItemCount,
            items
        );
    }
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

代码解释

这是一个基于 MediatR 模式的查询处理器(Query Handler),用于分页搜索认证令牌(Auth Tokens)

核心功能

该处理器实现了 SearchAuthTokensQuery 查询请求的处理逻辑,返回分页的令牌列表。

代码结构分析

1. 类定义与依赖注入

public class SearchAuthTokensQueryHandler(
    IRepository<DpzToken> tokenRepository,
    IRepository<DpzApplication> applicationRepository
) : IRequestHandler<SearchAuthTokensQuery, PagedListWarp<AuthTokenPageItem>>
  • 使用 C# 12 主构造函数语法注入两个仓储:
    • tokenRepository: 令牌数据仓储
    • applicationRepository: 应用程序数据仓储
  • 实现 IRequestHandler 接口,处理查询并返回分页结果

2. 查询逻辑流程

步骤 1: 构建基础查询

var query = tokenRepository.SearchFor(x => true);

初始化查询(获取所有令牌)

步骤 2: 条件过滤

// 按用户ID过滤
if (!string.IsNullOrWhiteSpace(request.UserId))
    query = query.Where(x => x.Subject == request.UserId);

// 按应用客户端ID过滤
if (!string.IsNullOrWhiteSpace(request.ClientId) && ObjectId.TryParse(request.ClientId, out var appId))
    query = query.Where(x => x.ApplicationId == appId);

根据请求参数动态添加过滤条件

步骤 3: 分页查询

var pagedList = await query
    .OrderByDescending(x => x.Id)  // 按ID降序排序
    .ToPagedListAsync(request.Page, request.Limit, cancellationToken);

步骤 4: 关联查询应用信息

var applicationIds = pagedList.Select(x => x.ApplicationId).ToHashSet();
var applications = await applicationRepository
    .SearchFor(Builders<DpzApplication>.Filter.In(x => x.Id, applicationIds))
    .ToListAsync(cancellationToken);
  • 提取所有令牌关联的应用ID
  • 批量查询应用信息(避免 N+1 查询问题

步骤 5: 数据映射

var items = pagedList.Select(x => {
    var app = applications.FirstOrDefault(y => y.Id == x.ApplicationId);
    return new AuthTokenPageItem {
        ApplicationName = app?.DisplayName,  // 关联应用名称
        // ... 其他字段映射
    };
}).ToList();

将数据库实体转换为 DTO(AuthTokenPageItem),并补充应用名称

步骤 6: 返回分页结果

return new PagedListWarp<AuthTokenPageItem>(
    pagedList.CurrentPageIndex,
    pagedList.PageSize,
    pagedList.TotalItemCount,
    items
);

设计亮点

  1. 仓储模式:数据访问逻辑解耦
  2. MediatR 中介者模式:请求/处理分离
  3. 性能优化:批量加载关联数据,避免 N+1 查询
  4. 动态查询:根据参数灵活过滤
  5. 异步操作:使用 async/await 提高并发性能
  6. 取消令牌支持:允许取消长时间运行的查询

应用场景

适用于OAuth 2.0 / OpenID Connect 令牌管理系统,提供:

  • 管理员查看所有令牌
  • 按用户/应用过滤令牌
  • 令牌审计和监控功能
评论加载中...