using Dpz.Core.Public.ViewModel.Request;
using Dpz.Core.Public.ViewModel.RequestEvent;
using Dpz.Core.Public.ViewModel.Response;
using Medallion.Threading;
namespace Dpz.Core.Service.RepositoryServiceImpl;
public class MumbleService(
IRepository<Mumble> repository,
IMapper mapper,
IMediator mediator,
IFusionCache fusionCache,
IDistributedLockProvider distributedLockProvider
) : AbstractCacheService(fusionCache), IMumbleService
{
protected override string CachePrefixKey => ServiceCacheKeyPrefixes.MumbleService;
protected override TimeSpan CacheDefaultExpiration => TimeSpan.FromDays(3);
private readonly IFusionCache _fusionCache = fusionCache;
/// <summary>
/// 点赞缓存标签前缀
/// </summary>
private string LikeTag => CachePrefixKey + ":Like";
/// <summary>
/// 生成点赞缓存键,形如 {Tag}:{Id}
/// </summary>
private string BuildLikeCacheKey(string mumbleId) => $"{LikeTag}:{mumbleId}";
/// <summary>
/// 生成点赞更新锁键,避免并发冲突
/// </summary>
private string BuildLikeLockKey(string mumbleId) => $"{LikeTag}:Lock:{mumbleId}";
/// <summary>
/// 评论数缓存标签前缀
/// </summary>
private string CommentTag => CachePrefixKey + ":Comment";
/// <summary>
/// 生成评论数缓存键,形如 {Tag}:{Id}
/// </summary>
private string BuildCommentCacheKey(string mumbleId) => $"{CommentTag}:{mumbleId}";
private async Task ClearCacheAsync()
{
var tags = new[]
{
CachePrefixKey + nameof(GetPagesAsync),
CachePrefixKey + nameof(GetRandomMumblesAsync),
CachePrefixKey + nameof(FindAsync),
LikeTag,
CommentTag,
};
await _fusionCache.RemoveByTagAsync(tags);
}
public async Task<MumbleResponse> CreateAsync(CreateMumbleRequest request)
{
var entity = mapper.Map<Mumble>(request);
await repository.InsertAsync(entity);
await ClearCacheAsync();
return mapper.Map<MumbleResponse>(entity);
}
public async Task<MumbleResponse?> EditContentAsync(EditMumbleRequest request)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
if (request.Markdown == null)
{
throw new ArgumentNullException(nameof(request.Markdown));
}
var entity = await repository.TryGetAsync(request.Id);
if (entity == null)
{
return null;
}
var editMarkdownRequest = new EditMarkdownRequest
{
Markdown = request.Markdown,
OriginalMarkdown = entity.Markdown,
};
await mediator.Send(editMarkdownRequest);
var update = Builders<Mumble>
.Update.Set(x => x.Markdown, request.Markdown)
.Set(x => x.LastUpdateTime, DateTime.Now);
await repository.UpdateAsync(x => x.Id == entity.Id, update);
await ClearCacheAsync();
return mapper.Map<MumbleResponse>(await repository.FindAsync(entity.Id));
}
public async Task<IPagedList<MumbleResponse>> GetPagesAsync(
int pageIndex,
int pageSize,
string? content = null,
string? account = null
)
{
var pagedList = await GetOrSetPagedListAsync(
nameof(GetPagesAsync),
async _ =>
{
var predicate = repository.SearchFor(x => true);
if (!string.IsNullOrEmpty(content))
{
predicate = predicate.Where(x => x.Markdown.Contains(content));
}
if (!string.IsNullOrEmpty(account))
{
predicate = predicate.Where(x => x.Author.Id == account);
}
return await predicate
.OrderByDescending(x => x.CreateTime)
.ToPagedListAsync<Mumble, MumbleResponse>(pageIndex, pageSize);
},
new
{
content,
account,
pageIndex,
pageSize,
}
);
await ApplyLikeCountsAsync(pagedList);
return pagedList;
}
public async Task<List<MumbleResponse>> GetRandomMumblesAsync(int count)
{
var cache = await GetOrSetCacheAsync<List<MumbleResponse>>(
nameof(GetRandomMumblesAsync),
async (_, cancellationToken) =>
{
var date = DateTime.Now;
var data = await repository
.SearchFor(x =>
x.CreateTime.Month == date.Month
&& x.CreateTime.Day == date.Day
&& x.CreateTime.Year != date.Year
)
.Take(count)
.OrderByDescending(x => x.CreateTime)
.ToListAsync(cancellationToken);
if (data.Count == 0)
{
data = await repository
.SearchFor(x => true)
.Sample(count)
.ToListAsync(cancellationToken: cancellationToken);
}
return mapper.Map<List<MumbleResponse>>(data);
},
new { count },
x => x.SetDuration(TimeSpan.FromHours(3))
);
await ApplyLikeCountsAsync(cache);
return cache;
}
public async Task<MumbleResponse?> FindAsync(string id)
{
var cache = await GetOrSetCacheAsync<MumbleResponse?>(
nameof(FindAsync),
async (_, _) => mapper.Map<MumbleResponse?>(await repository.TryGetAsync(id)),
new { id }
);
await ApplyLikeCountAsync(cache);
return cache;
}
public async Task DeleteAsync(params string[] id)
{
var list = await repository.TryGetListAsync(id);
var request = new DeleteMarkdownRequest
{
Markdown = list.Select(x => x.Markdown).ToList(),
};
await mediator.Send(request);
await repository.TryDeleteAsync(id);
await ClearCacheAsync();
}
public async Task<MumbleResponse?> LikeAsync(string id)
{
var entity = await repository.TryGetAsync(id);
if (entity is null)
{
return null;
}
await using (await distributedLockProvider.AcquireLockAsync(BuildLikeLockKey(id)))
{
var current = entity.Like;
var update = Builders<Mumble>.Update.Inc(x => x.Like, 1);
await repository.UpdateAsync(x => x.Id == entity.Id, update);
var next = checked(current + 1);
entity.Like = next;
await SetLikeCacheAsync(id, next);
await UpdateCachedMumbleLikeAsync(id, next);
return mapper.Map<MumbleResponse>(entity);
}
}
/// <summary>
/// 应用最新的点赞数、评论数到列表中,保证缓存命中也能看到最新值
/// </summary>
private async Task ApplyLikeCountsAsync(IEnumerable<MumbleResponse>? mumbles)
{
if (mumbles == null)
{
return;
}
foreach (var mumble in mumbles)
{
if (string.IsNullOrWhiteSpace(mumble.Id))
{
continue;
}
mumble.Like = await EnsureLikeCacheAsync(mumble.Id, mumble.Like);
mumble.CommentCount = await EnsureCommentCacheAsync(mumble.Id, mumble.CommentCount);
}
}
/// <summary>
/// 应用最新的点赞数、评论数到单个对象
/// </summary>
private async Task ApplyLikeCountAsync(MumbleResponse? mumble)
{
if (mumble == null || string.IsNullOrWhiteSpace(mumble.Id))
{
return;
}
mumble.Like = await EnsureLikeCacheAsync(mumble.Id, mumble.Like);
mumble.CommentCount = await EnsureCommentCacheAsync(mumble.Id, mumble.CommentCount);
}
/// <summary>
/// 保证点赞缓存存在,若缺失则使用后备值初始化
/// </summary>
private async Task<int> EnsureLikeCacheAsync(string mumbleId, int fallbackValue)
{
var cache = await _fusionCache.TryGetAsync<int>(BuildLikeCacheKey(mumbleId));
if (cache.HasValue)
{
return cache.Value;
}
await SetLikeCacheAsync(mumbleId, fallbackValue);
return fallbackValue;
}
/// <summary>
/// 设置点赞缓存,并带上点赞相关标签
/// </summary>
private ValueTask SetLikeCacheAsync(string mumbleId, int likeCount)
{
return _fusionCache.SetAsync(
BuildLikeCacheKey(mumbleId),
likeCount,
options => options.SetDuration(CacheDefaultExpiration),
new[] { CachePrefixKey, LikeTag }
);
}
/// <summary>
/// 保证评论缓存存在,若缺失则使用后备值初始化
/// </summary>
private async Task<int> EnsureCommentCacheAsync(string mumbleId, int fallbackValue)
{
var cache = await _fusionCache.TryGetAsync<int>(BuildCommentCacheKey(mumbleId));
if (cache.HasValue)
{
return cache.Value;
}
await SetCommentCacheAsync(mumbleId, fallbackValue);
return fallbackValue;
}
/// <summary>
/// 设置评论缓存,并带上评论相关标签
/// </summary>
private ValueTask SetCommentCacheAsync(string mumbleId, int commentCount)
{
return _fusionCache.SetAsync(
BuildCommentCacheKey(mumbleId),
commentCount,
options => options.SetDuration(CacheDefaultExpiration),
[CachePrefixKey, CommentTag]
);
}
/// <summary>
/// 更新已缓存的碎碎念详情中的点赞值
/// </summary>
private async Task UpdateCachedMumbleLikeAsync(string mumbleId, int likeCount)
{
var cached = await TryGetFromCacheAsync<MumbleResponse?>(
nameof(FindAsync),
new { id = mumbleId }
);
if (cached is { HasValue: true, Value: not null })
{
cached.Value.Like = likeCount;
await SetCacheAsync(nameof(FindAsync), cached.Value, new { id = mumbleId });
}
}
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
MumbleService 代码解析
这个 MumbleService 类是一个处理"碎碎念"(Mumble)相关业务逻辑的服务类,主要功能包括创建、编辑、查询和删除碎碎念,以及点赞和评论计数管理等。
主要功能
基础CRUD操作:
CreateAsync: 创建新的碎碎念EditContentAsync: 编辑碎碎念内容FindAsync: 根据ID查找单个碎碎念DeleteAsync: 删除碎碎念
查询功能:
GetPagesAsync: 分页获取碎碎念列表,支持按内容和作者过滤GetRandomMumblesAsync: 获取随机碎碎念(优先获取历史同月同日的碎碎念)
互动功能:
LikeAsync: 点赞碎碎念,使用分布式锁防止并发问题
缓存管理:
- 使用FusionCache进行多级缓存
- 点赞数和评论数单独缓存
- 缓存自动失效和更新机制
缓存设计
多级缓存:
- 继承自
AbstractCacheService,提供基础缓存功能 - 默认缓存过期时间为3天(
CacheDefaultExpiration)
- 继承自
缓存键设计:
- 点赞缓存:
{CachePrefixKey}:Like:{Id} - 评论数缓存:
{CachePrefixKey}:Comment:{Id} - 分布式锁键:
{CachePrefixKey}:Like:Lock:{Id}
- 点赞缓存:
缓存失效:
- 任何修改操作后调用
ClearCacheAsync清除相关缓存 - 使用标签批量清除相关缓存
- 任何修改操作后调用
点赞系统实现
并发控制:
- 使用
IDistributedLockProvider确保点赞操作的原子性 - 通过
AcquireLockAsync获取分布式锁
- 使用
缓存一致性:
- 点赞后更新数据库和缓存
- 通过
UpdateCachedMumbleLikeAsync保持缓存数据最新
计数保证:
EnsureLikeCacheAsync确保缓存中存在点赞数ApplyLikeCountsAsync和ApplyLikeCountAsync确保返回的数据包含最新计数
其他特点
使用MediatR:
- 通过
IMediator发送EditMarkdownRequest和DeleteMarkdownRequest等事件
- 通过
自动映射:
- 使用
IMapper进行DTO和实体之间的转换
- 使用
异常处理:
- 对空参数进行校验并抛出
ArgumentNullException
- 对空参数进行校验并抛出
这个服务类设计合理,考虑了性能(缓存)、并发安全(分布式锁)和数据一致性,是一个典型的中大型应用中的服务层实现。
评论加载中...