using Dpz.Core.MessageQueue.Abstractions;
using Dpz.Core.Public.ViewModel.Messages;
using Dpz.Core.Public.ViewModel.Request;
using Dpz.Core.Public.ViewModel.Response;
namespace Dpz.Core.Service.RepositoryServiceImpl;
public class PictureRecordService(
IRepository<PictureRecord> repository,
IMapper mapper,
IFusionCache fusionCache,
IMessagePublisher<RemoveImagesMessage> removeImagesPublisher
) : IPictureRecordService
{
private readonly IFusionCache _fusionCache = fusionCache;
[Cache(ExpirationSeconds = 1 * ExpirationTime.Day)]
public async Task<List<string>> GetTagsAsync(CancellationToken cancellationToken = default)
{
var tags = await repository
.MongodbQueryable
//.Select(x => x.Tags)
.SelectMany(x => x.Tags)
.GroupBy(x => x)
.Select(x => x.Key)
.ToListAsync(cancellationToken);
return tags;
}
[Cache(ExpirationSeconds = 1 * ExpirationTime.Day)]
public async Task<ICollection<PictureRecordResponse>> GetBannerAsync()
{
var pagedList = await repository
.SearchFor(x => x.Tags.Any(y => y == "banner"))
.OrderByDescending(x => x.UploadTime)
.ToListAsync<PictureRecord, PictureRecordResponse>();
pagedList.ForEach(x => x.AccessUrl += "!banner");
return pagedList;
}
[Cache(ExpirationSeconds = 1 * ExpirationTime.Day)]
public async Task<IPagedList<PictureRecordResponse>> GetPagesAsync(
List<string>? tags,
string? description,
int pageIndex = 1,
int pageSize = 20,
string? account = null,
CancellationToken cancellationToken = default
)
{
var filterEmpty = Builders<PictureRecord>.Filter.Empty;
var filters = new List<FilterDefinition<PictureRecord>>();
var clearTags = tags?.Where(x => !string.IsNullOrEmpty(x)).Select(x => x).ToList() ?? [];
if (clearTags.Count > 0)
{
var filter = Builders<PictureRecord>.Filter.AnyIn(x => x.Tags, clearTags);
filters.Add(filter);
}
if (!string.IsNullOrEmpty(description))
{
var filter = Builders<PictureRecord>.Filter.Regex(
x => x.Description,
new BsonRegularExpression(description, "i")
);
filters.Add(filter);
}
if (!string.IsNullOrEmpty(account))
{
var filter = Builders<PictureRecord>.Filter.Eq(x => x.Creator.Id, account);
filters.Add(filter);
}
var filterResult =
filters.Count > 0 ? Builders<PictureRecord>.Filter.And(filters) : filterEmpty;
return await repository
.SearchFor(filterResult)
.SortByDescending(x => x.Id)
.ToPagedListAsync<PictureRecord, PictureRecordResponse>(
pageIndex,
pageSize,
cancellationToken: cancellationToken
);
}
[Cache(ExpirationSeconds = 1 * ExpirationTime.Day)]
public async Task<PictureRecordResponse?> GetPictureRecordAsync(string id)
{
var entity = await repository.TryGetAsync(id);
return entity == null ? null : mapper.Map<PictureRecordResponse>(entity);
}
[InvalidateCache(
Methods = [
nameof(GetTagsAsync),
nameof(GetPagesAsync),
nameof(GetBannerAsync),
nameof(GetPictureRecordAsync),
]
)]
public async Task<PictureRecordResponse> CreateRecordAsync(
CreatePictureRecordRequest request,
CancellationToken cancellationToken = default
)
{
var entity = mapper.Map<PictureRecord>(request);
await repository.InsertAsync(entity, cancellationToken);
await RemoveHomeContainerIfBannerAsync(entity.Tags, cancellationToken);
return mapper.Map<PictureRecordResponse>(entity);
}
[InvalidateCache(
Methods = [
nameof(GetTagsAsync),
nameof(GetPagesAsync),
nameof(GetBannerAsync),
nameof(GetPictureRecordAsync),
]
)]
public async Task<PictureRecordResponse?> ChangeInformationAsync(
EditPictureRecordRequest request,
CancellationToken cancellationToken = default
)
{
if (!ObjectId.TryParse(request.Id, out var oid))
{
return null;
}
var update = Builders<PictureRecord>
.Update.Set(x => x.Category, request.Category)
.Set(x => x.Description, request.Description)
.Set(x => x.Tags, request.Tags);
await repository.UpdateAsync(x => x.Id == oid, update, cancellationToken);
await RemoveHomeContainerIfBannerAsync(request.Tags, cancellationToken);
var entity = await repository.FindAsync(oid, cancellationToken);
return entity == null ? null : mapper.Map<PictureRecordResponse>(entity);
}
[InvalidateCache(
Methods = [
nameof(GetTagsAsync),
nameof(GetPagesAsync),
nameof(GetBannerAsync),
nameof(GetPictureRecordAsync),
]
)]
public async Task DeleteAsync(string id)
{
var entity = await repository.TryGetAsync(id);
if (entity == null)
{
return;
}
await repository.DeleteAsync(entity.Id);
// 发布消息到队列,异步删除图片
if (!string.IsNullOrEmpty(entity.AccessUrl))
{
await removeImagesPublisher.PublishAsync(
new RemoveImagesMessage { ImageUrl = entity.AccessUrl }
);
}
}
[InvalidateCache(
Methods = [
nameof(GetTagsAsync),
nameof(GetPagesAsync),
nameof(GetBannerAsync),
nameof(GetPictureRecordAsync),
]
)]
public Task ClearCacheAsync(CancellationToken cancellationToken = default)
{
return Task.CompletedTask;
}
[InvalidateCache(
Methods = [
nameof(GetTagsAsync),
nameof(GetPagesAsync),
nameof(GetBannerAsync),
nameof(GetPictureRecordAsync),
]
)]
public async Task ClearPublishCacheAsync(
IReadOnlyCollection<string> tags,
CancellationToken cancellationToken = default
)
{
await RemoveHomeContainerIfBannerAsync(tags, cancellationToken);
}
private async Task RemoveHomeContainerIfBannerAsync(
IReadOnlyCollection<string> tags,
CancellationToken cancellationToken = default
)
{
if (tags.Any(x => string.Equals(x, "banner", StringComparison.OrdinalIgnoreCase)))
{
await _fusionCache.RemoveAsync(CacheKey.HomeContainerKey, token: cancellationToken);
}
}
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
代码解释
这是一个图片记录服务类(PictureRecordService),实现了 IPictureRecordService 接口,用于管理图片记录的 CRUD 操作和缓存管理。
核心功能模块
1. 依赖注入(构造函数)
public PictureRecordService(
IRepository<PictureRecord> repository, // 数据仓储
IMapper mapper, // 对象映射器
IFusionCache fusionCache, // 缓存管理
IMessagePublisher<RemoveImagesMessage> removeImagesPublisher // 消息发布器
)
2. 主要方法说明
GetTagsAsync - 获取所有标签
- 使用 MongoDB 查询所有图片的标签
- 通过
SelectMany展平标签数组,GroupBy去重 - 缓存1天
GetBannerAsync - 获取轮播图
- 筛选包含 "banner" 标签的图片
- 按上传时间倒序排列
- 为访问 URL 添加
!banner后缀(可能用于图片裁剪/样式) - 缓存1天
GetPagesAsync - 分页查询图片
参数:
- tags: 标签过滤(支持多个)
- description: 描述模糊搜索(正则表达式,忽略大小写)
- account: 创建者筛选
- pageIndex/pageSize: 分页参数
使用 MongoDB FilterDefinition 构建动态查询条件。
GetPictureRecordAsync - 获取单个图片记录
- 根据 ID 查询
- 缓存1天
CreateRecordAsync - 创建图片记录
- 映射请求对象到实体
- 插入数据库
- 如果包含 "banner" 标签,清除首页容器缓存
- 清除相关缓存(通过
InvalidateCache特性)
ChangeInformationAsync - 修改图片信息
- 验证 ObjectId 格式
- 使用 MongoDB Update Builder 更新字段:
- Category(分类)
- Description(描述)
- Tags(标签)
- 清除相关缓存
DeleteAsync - 删除图片记录
- 删除数据库记录
- 异步发布消息到队列,由后台服务删除实际图片文件
- 清除相关缓存
ClearCacheAsync - 手动清除缓存
- 触发缓存失效(通过
InvalidateCache特性实现)
ClearPublishCacheAsync - 清除发布相关缓存
- 根据标签判断是否需要清除首页容器缓存
3. 关键设计模式
缓存策略
- 使用
[Cache]特性标记需要缓存的方法 - 使用
[InvalidateCache]特性自动清除相关缓存 - 写操作(Create/Update/Delete)会使所有读操作的缓存失效
消息队列
await removeImagesPublisher.PublishAsync(
new RemoveImagesMessage { ImageUrl = entity.AccessUrl }
);
采用异步消息机制删除图片文件,避免阻塞主流程。
动态查询构建
使用 MongoDB Builders 模式构建灵活的查询条件:
var filters = new List<FilterDefinition<PictureRecord>>();
// 根据条件动态添加过滤器
var filterResult = filters.Count > 0
? Builders<PictureRecord>.Filter.And(filters)
: filterEmpty;
4. 特殊逻辑
Banner 标签特殊处理:
- 任何涉及 "banner" 标签的操作都会清除首页容器缓存(
CacheKey.HomeContainerKey) - 确保首页轮播图实时更新
技术栈
- MongoDB:数据存储
- FusionCache:多级缓存
- AutoMapper:对象映射
- 消息队列:异步图片删除
- AOP:缓存切面(通过特性实现)
AI 正在分析代码…
评论加载中...