using Dpz.Core.Public.Entity.Auth;

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

public class HandleApplicationAccessRequestCommandHandler(
    IRepository<ApplicationAccessRequest> accessRequestRepository,
    IRepository<AllowedClient> allowedClientRepository,
    IRepository<User> userRepository
) : IRequestHandler<HandleApplicationAccessRequestCommand, ResponseResult>
{
    public async ValueTask<ResponseResult> Handle(
        HandleApplicationAccessRequestCommand request,
        CancellationToken cancellationToken
    )
    {
        if (!ObjectId.TryParse(request.Id, out var oid))
        {
            return ResponseResult.Fail("Invalid request id");
        }

        var accessRequest = await accessRequestRepository.FindAsync(oid, cancellationToken);
        if (accessRequest == null)
        {
            return ResponseResult.Fail("Request not found", 404);
        }

        var handler = string.IsNullOrWhiteSpace(request.HandlerAccount)
            ? null
            : await userRepository
                .SearchFor(x => x.Id == request.HandlerAccount)
                .FirstOrDefaultAsync(cancellationToken);
        if (handler == null)
        {
            return ResponseResult.Fail("Handler not found");
        }

        if (request.Status == AccessRequestStatus.Pending)
        {
            if (accessRequest.Status == AccessRequestStatus.Approved)
            {
                await DeleteAllowedClientAsync(accessRequest, cancellationToken);
            }

            await accessRequestRepository.UpdateAsync(
                x => x.Id == oid,
                Builders<ApplicationAccessRequest>
                    .Update.Set(x => x.Status, AccessRequestStatus.Pending)
                    .Set(x => x.HandleTime, null)
                    .Set(x => x.HandlerId, null)
                    .Set(x => x.HandlerName, null)
                    .Set(x => x.HandleRemark, null),
                cancellationToken
            );

            return ResponseResult.Ok("处理成功");
        }

        if (
            accessRequest.Status == AccessRequestStatus.Approved
            && request.Status != AccessRequestStatus.Approved
        )
        {
            await DeleteAllowedClientAsync(accessRequest, cancellationToken);
        }

        var handleTime = DateTime.Now;
        await accessRequestRepository.UpdateAsync(
            x => x.Id == oid,
            Builders<ApplicationAccessRequest>
                .Update.Set(x => x.Status, request.Status)
                .Set(x => x.HandleTime, handleTime)
                .Set(x => x.HandlerId, handler.Id)
                .Set(x => x.HandlerName, handler.Name)
                .Set(x => x.HandleRemark, request.Remark),
            cancellationToken
        );

        if (request.Status == AccessRequestStatus.Approved)
        {
            await AddAllowedClientAsync(accessRequest, cancellationToken);
        }

        return ResponseResult.Ok("处理成功");
    }

    private async Task DeleteAllowedClientAsync(
        ApplicationAccessRequest accessRequest,
        CancellationToken cancellationToken
    )
    {
        await allowedClientRepository.DeleteAsync(
            x => x.Account == accessRequest.UserId && x.ApplicationId == accessRequest.ClientId,
            cancellationToken
        );
    }

    private async Task AddAllowedClientAsync(
        ApplicationAccessRequest accessRequest,
        CancellationToken cancellationToken
    )
    {
        var exists = await allowedClientRepository
            .SearchFor(x =>
                x.Account == accessRequest.UserId && x.ApplicationId == accessRequest.ClientId
            )
            .AnyAsync(cancellationToken);
        if (exists)
        {
            return;
        }

        await allowedClientRepository.InsertAsync(
            new AllowedClient
            {
                Account = accessRequest.UserId,
                ApplicationId = accessRequest.ClientId,
                ApplicationName = accessRequest.ClientDisplayName,
                AllowedTime = DateTime.Now,
            },
            cancellationToken
        );
    }
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

这段代码是一个 C# 命令处理器,实现了对“应用访问请求”的审批操作。它主要应用于类似 OAuth / SSO 授权第三方应用接入 场景中,当某个用户申请访问某个客户端应用时,管理员通过此处理器决定同意、拒绝或取消处理该请求,并同步维护“允许客户端”(AllowedClient)数据。

下面从功能、代码结构、核心逻辑三个层面进行解释。


1. 类与依赖注入

public class HandleApplicationAccessRequestCommandHandler(...) : IRequestHandler<...>
  • 这是一个 MediatR 命令处理器,对应命令 HandleApplicationAccessRequestCommand
  • 构造函数使用 主构造函数(C# 12) 直接注入三个仓储依赖:
    • IRepository<ApplicationAccessRequest>:查询/更新访问请求记录。
    • IRepository<AllowedClient>:管理已被允许访问的客户端(授权记录)。
    • IRepository<User>:查找处理人(管理员)信息。
  • 仓储类型推测为 MongoDB 仓储,因为使用了 ObjectIdBuilders<...>.Update 等 MongoDB 特有类型。

2. 主处理流程(Handle 方法)

① 参数校验

  • 将前端传入的 request.Id 解析为 MongoDB 的 ObjectId,解析失败则返回失败。
  • 根据 Id 从仓储查询对应的“访问请求”记录,若不存在则返回 404 Not Found

② 处理人(管理员)校验

  • request.HandlerAccount(可能是用户 Id 或账号)查找对应的 User
  • 若为空,则返回“处理人不存在”。

③ 情况一:新状态为 Pending(重新挂起)

  • 如果原状态已是 Approved,则会先删除已存在的 AllowedClient,即撤销该用户对此应用的访问权。
  • 然后将访问请求的状态重置为 Pending,并清空之前的处理时间、处理人 Id、处理人姓名和处理备注。
  • 相当于将请求“退回到待审批”状态,恢复为未处理。

④ 情况二:从 Approved 改为其他非批准状态(如拒绝)

  • 若原状态已是 Approved,但新状态不是 Approved,同样会删除 AllowedClient,即撤销已经开通的访问权限。

⑤ 更新访问请求记录

  • 无论最终状态如何(除 Pending 分支已提前返回),都会更新该请求的状态、处理时间、处理人信息、备注。

⑥ 情况三:新状态为 Approved(批准)

  • 批准通过后,调用 AddAllowedClientAsync 为对应用户新增一条允许访问该客户端的记录。
  • 新增前会检查是否已存在,只有不存在时才插入,避免重复授权。

最后返回“处理成功”。


3. 私有辅助方法

DeleteAllowedClientAsync

  • 根据请求中的 UserIdClientId,从 AllowedClient 集合中删除对应的授权记录。
  • 主要用于撤销访问权。

AddAllowedClientAsync

  • 先查询是否已有 Account == UserId && ApplicationId == ClientId 的授权记录;
  • 若已存在则不重复添加,否则新建一条 AllowedClient 并保存。
  • 新增字段包含用户、应用、应用显示名、授权时间。

4. 整体设计意图

  • 状态管理:访问请求的流转状态至少包含 PendingApprovedRejected(或类似),该处理器负责状态的转换和副作用处理。
  • 授权同步:状态一旦变为“已批准”则创建授权记录;一旦“被拒绝”或“被重置为待处理”,则删除授权记录。
  • 幂等性 & 安全性
    • 使用 ObjectId.TryParse 避免非法 ID。
    • 查找处理人时防止无权限或空操作。
    • AddAllowedClientAsync 内部检查重复,保证不会产生重复授权。
    • DeleteAllowedClientAsync 通过精确条件删除,不误删。

5. 典型应用场景举例

假设一个用户在系统中申请使用某个第三方应用(比如企业微信、钉钉),管理员收到申请后:

  • 若批准:系统会为该用户建立一条 AllowedClient,使其能获取访问令牌;
  • 若拒绝:系统清除已有的授权(如果之前曾批准),用户无法继续访问;
  • 若重新置为“待处理”:相当于撤销当前审核结果,之后可以重新审批。

6. 可扩展/注意点

  • 代码中直接使用 DateTime.Now,若需时间统一性可考虑 DateTime.UtcNow
  • 多次审批/状态变化的并发控制未显式处理(例如乐观锁),在高并发下可能产生状态覆盖。
  • 删除授权记录后如需记录日志,可在后续版本中增加审计。
  • 建议对 request.Status 增加枚举校验,避免非法值。

总体而言,这是一个清晰、职责单一的命令处理器,它将“审批操作”与“授权数据变更”完整封装,是业务层中负责“应用访问授权审批”的核心逻辑。

评论加载中...