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 仓储,因为使用了
ObjectId、Builders<...>.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
- 根据请求中的
UserId和ClientId,从AllowedClient集合中删除对应的授权记录。 - 主要用于撤销访问权。
AddAllowedClientAsync
- 先查询是否已有
Account == UserId && ApplicationId == ClientId的授权记录; - 若已存在则不重复添加,否则新建一条
AllowedClient并保存。 - 新增字段包含用户、应用、应用显示名、授权时间。
4. 整体设计意图
- 状态管理:访问请求的流转状态至少包含
Pending、Approved、Rejected(或类似),该处理器负责状态的转换和副作用处理。 - 授权同步:状态一旦变为“已批准”则创建授权记录;一旦“被拒绝”或“被重置为待处理”,则删除授权记录。
- 幂等性 & 安全性:
- 使用
ObjectId.TryParse避免非法 ID。 - 查找处理人时防止无权限或空操作。
AddAllowedClientAsync内部检查重复,保证不会产生重复授权。DeleteAllowedClientAsync通过精确条件删除,不误删。
- 使用
5. 典型应用场景举例
假设一个用户在系统中申请使用某个第三方应用(比如企业微信、钉钉),管理员收到申请后:
- 若批准:系统会为该用户建立一条
AllowedClient,使其能获取访问令牌; - 若拒绝:系统清除已有的授权(如果之前曾批准),用户无法继续访问;
- 若重新置为“待处理”:相当于撤销当前审核结果,之后可以重新审批。
6. 可扩展/注意点
- 代码中直接使用
DateTime.Now,若需时间统一性可考虑DateTime.UtcNow。 - 多次审批/状态变化的并发控制未显式处理(例如乐观锁),在高并发下可能产生状态覆盖。
- 删除授权记录后如需记录日志,可在后续版本中增加审计。
- 建议对
request.Status增加枚举校验,避免非法值。
总体而言,这是一个清晰、职责单一的命令处理器,它将“审批操作”与“授权数据变更”完整封装,是业务层中负责“应用访问授权审批”的核心逻辑。
AI 正在分析代码…
评论加载中...