namespace Dpz.Core.Service.RepositoryServiceImpl;

public class BlockService(IRepository<Block> repository, IFusionCache fusionCache, IMapper mapper)
    : IBlockService
{
    public async Task SaveBlockAsync(Block block)
    {
        var entity = await repository
            .SearchFor(x =>
                x.RequestMethod == block.RequestMethod && x.RequestPath == block.RequestPath
            )
            .FirstOrDefaultAsync();
        if (entity != null)
        {
            var updates = new List<UpdateDefinition<Block>>
            {
                Builders<Block>.Update.Inc(x => x.AccessCount, 1),
            };
            var pushIpAddresses = block.IpAddresses.Except(entity.IpAddresses).ToList();
            if (pushIpAddresses.Count > 0)
            {
                updates.Add(Builders<Block>.Update.PushEach(x => x.IpAddresses, pushIpAddresses));
            }

            var pushUserAgents = block.UserAgents.Except(entity.UserAgents).ToList();
            if (pushUserAgents.Count > 0)
            {
                updates.Add(Builders<Block>.Update.PushEach(x => x.UserAgents, pushUserAgents));
            }

            await repository.UpdateAsync(
                x => x.Id == entity.Id,
                Builders<Block>.Update.Combine(updates)
            );
            return;
        }

        if (block.AccessCount <= 0)
        {
            block.AccessCount = 1;
        }
        await repository.InsertAsync(block);
    }

    public async Task IncrementCountAsync(string method, string requestPath)
    {
        var block = await repository
            .SearchFor(x => x.RequestMethod == method && x.RequestPath == requestPath)
            .FirstOrDefaultAsync();
        if (block == null)
        {
            await SaveBlockAsync(new Block { RequestMethod = method, RequestPath = requestPath });
            return;
        }

        var update = Builders<Block>.Update.Inc(x => x.AccessCount, 1);
        await repository.UpdateAsync(x => x.Id == block.Id, update);
    }

    public async Task PushIpAddressAsync(
        string method,
        string requestPath,
        params string[] ipAddresses
    )
    {
        if (string.IsNullOrEmpty(method))
        {
            throw new ArgumentNullException(nameof(method));
        }

        if (string.IsNullOrEmpty(requestPath))
        {
            throw new ArgumentNullException(nameof(requestPath));
        }

        var block = await repository
            .SearchFor(x => x.RequestMethod == method && x.RequestPath == requestPath)
            .FirstOrDefaultAsync();
        if (block == null)
        {
            await SaveBlockAsync(
                new Block
                {
                    RequestMethod = method,
                    RequestPath = requestPath,
                    IpAddresses = ipAddresses,
                }
            );
            return;
        }

        var pushIpAddresses = ipAddresses.Except(block.IpAddresses).ToList();
        if (pushIpAddresses.Count == 0)
        {
            return;
        }

        var update = Builders<Block>
            .Update.PushEach(x => x.IpAddresses, pushIpAddresses)
            .Inc(x => x.AccessCount, 1);
        await repository.UpdateAsync(x => x.Id == block.Id, update);
    }

    public async Task PushUserAgentAsync(
        string method,
        string requestPath,
        params string[] userAgents
    )
    {
        if (string.IsNullOrEmpty(method))
        {
            throw new ArgumentNullException(nameof(method));
        }

        if (string.IsNullOrEmpty(requestPath))
        {
            throw new ArgumentNullException(nameof(requestPath));
        }

        var block = await repository
            .SearchFor(x => x.RequestMethod == method && x.RequestPath == requestPath)
            .FirstOrDefaultAsync();
        if (block == null)
        {
            await SaveBlockAsync(
                new Block
                {
                    RequestMethod = method,
                    RequestPath = requestPath,
                    UserAgents = userAgents,
                }
            );
            return;
        }

        var pushUserAgents = userAgents.Except(block.UserAgents).ToList();
        if (pushUserAgents.Count == 0)
        {
            return;
        }

        var update = Builders<Block>
            .Update.PushEach(x => x.UserAgents, pushUserAgents)
            .Inc(x => x.AccessCount, 1);
        await repository.UpdateAsync(x => x.Id == block.Id, update);
    }

    public async Task<List<VmBlock>> GetBlocksAsync(int? minAccess = null)
    {
        var key = $"Block:List:{(minAccess is null or <= 0 ? "All" : minAccess)}";
        var cache = await fusionCache.TryGetAsync<List<VmBlock>>(key);
        if (cache.HasValue)
        {
            return cache.Value;
        }

        var minAccessValue = minAccess ?? 0;
        var list = await repository.SearchFor(x => x.AccessCount >= minAccessValue).ToListAsync();
        var source = mapper.Map<List<VmBlock>>(list);
        await fusionCache.SetAsync(key, source, TimeSpan.FromHours(3));
        return source;
    }
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

这段代码实现了对“Block”实体的一组增删改查和缓存读取操作(封装在 Dpz.Core.Service.RepositoryServiceImpl.BlockService 中)。主要职责是记录/更新某个 HTTP 请求(由 RequestMethod + RequestPath 唯一标识)的访问次数、来源 IP 和 User-Agent,并提供带缓存的读取接口。下面逐方法解释并补充一些实现细节与注意点。

总体依赖

  • IRepository repository:数据存取层(看起来是基于 MongoDB 的仓储,代码中用到了 Builders、UpdateDefinition 等)。
  • IFusionCache fusionCache:缓存层(用于 GetBlocksAsync 的结果缓存)。
  • IMapper mapper:用于将实体映射为 VmBlock DTO(AutoMapper 风格)。

方法说明

  1. SaveBlockAsync(Block block)
  • 先按 RequestMethod + RequestPath 在仓储中查找是否已有记录(SearchFor(...).FirstOrDefaultAsync)。
  • 如果存在(entity != null):
    • 构造一个更新集合:至少对 AccessCount 做自增(Inc 1)。
    • 将传入 block.IpAddresses 中不在已有 entity.IpAddresses 的 IP 收集出来(Except),若有新增 IP 则用 PushEach 将它们追加到列表中。
    • 同理处理 UserAgents。
    • 用 repository.UpdateAsync 以 entity.Id 为条件,执行组合更新(Combine(updates))。
  • 如果不存在:
    • 确保 block.AccessCount 至少为 1(如果 <=0 则设为 1)。
    • 插入新文档:repository.InsertAsync(block)。

要点:

  • 对已有记录是以增量更新(Inc + PushEach)的方式,避免覆盖已有数据。
  • 使用 Except 去重只添加新的 IP/UserAgent。
  1. IncrementCountAsync(string method, string requestPath)
  • 查找对应的 block:
    • 若不存在,调用 SaveBlockAsync 创建一个新的 Block(仅包含 method/path)。
    • 若存在,仅对 AccessCount 做 Inc(1) 更新。
  1. PushIpAddressAsync(string method, string requestPath, params string[] ipAddresses)
  • 参数校验:method/requestPath 不能为 null/empty(否则抛 ArgumentNullException)。
  • 查找对应 block:
    • 若不存在,则用提供的 ipAddresses 创建新 Block 并保存(SaveBlockAsync)。
    • 若存在,则计算传入 ipAddresses 中不在 block.IpAddresses 的那些(Except)。如果没有新增则直接返回;否则用 PushEach 把新增 IP 添加到集合,同时 Inc(AccessCount, 1)。
  • 这一方法用于向已有记录追加新的来源 IP,并将访问计数递增。
  1. PushUserAgentAsync(string method, string requestPath, params string[] userAgents)
  • 与 PushIpAddressAsync 对应,但处理的是 UserAgents 列表。相同的参数校验、存在性处理、去重追加并自增访问计数。
  1. GetBlocksAsync(int? minAccess = null)
  • 构造缓存键 key = $"Block:List:{(minAccess is null or <= 0 ? "All" : minAccess)}"。
  • 先尝试从 fusionCache 读取缓存(TryGetAsync)。若命中则直接返回缓存值。
  • 若未命中,计算 minAccessValue(默认 0),从 repository 查询 AccessCount >= minAccessValue 的所有记录并 ToListAsync。
  • 使用 mapper 将实体列表映射为 List(DTO)。
  • 把结果写入 cache(SetAsync),过期时间 3 小时。
  • 返回 DTO 列表。

实现与潜在注意点

  • 数据库更新使用 Builders.Update 和 UpdateAsync(应当在 MongoDB 层是原子操作)。
  • 对 IP/UserAgent 的去重使用 Except,注意若 entity.IpAddresses 或 entity.UserAgents 为 null 会抛 NRE,代码中没有对这些集合为 null 的防护(假设实体初始化时为空集合)。
  • Except 的比较是基于默认字符串相等(区分大小写),如果需要不区分大小写或更复杂的比较,需调整。
  • SaveBlockAsync 中先查找再更新/插入存在竞态窗口:如果多个并发请求同时判断不存在,可能会造成多次 Insert。当前代码在 UpdateAsync 以 Id 为条件时是安全的,但并发创建仍需注意(可用唯一索引 + 捕获重复键异常,或使用 FindOneAndUpdate upsert 的原子操作)。
  • 缓存键包含 minAccess,合理避免缓存污染;缓存时长固定 3 小时,可根据场景调整。

总结 这个服务类负责记录和维护按请求方法 + 路径聚合的访问计数、来源 IP 列表和 User-Agent 列表,提供对这些数据的追加/计数操作,并通过 FusionCache 对查询结果做缓存,以提高读取性能。

评论加载中...