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 风格)。
方法说明
- 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。
- IncrementCountAsync(string method, string requestPath)
- 查找对应的 block:
- 若不存在,调用 SaveBlockAsync 创建一个新的 Block(仅包含 method/path)。
- 若存在,仅对 AccessCount 做 Inc(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,并将访问计数递增。
- PushUserAgentAsync(string method, string requestPath, params string[] userAgents)
- 与 PushIpAddressAsync 对应,但处理的是 UserAgents 列表。相同的参数校验、存在性处理、去重追加并自增访问计数。
- 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 对查询结果做缓存,以提高读取性能。
评论加载中...