using System.Text.RegularExpressions;
namespace Dpz.Core.Service.RepositoryServiceImpl;
public class BookmarkService(IRepository<Bookmark> repository, IMapper mapper) : IBookmarkService
{
[Cache(ExpirationSeconds = 30 * ExpirationTime.Day)]
public async Task<List<VmBookmark>> GetBookmarksAsync(string? title, string[]? categories)
{
var empty = Builders<Bookmark>.Filter.Empty;
var titleFilter = string.IsNullOrEmpty(title)
? empty
: Builders<Bookmark>.Filter.Regex(x => x.Title, new BsonRegularExpression(title, "i"));
var categoryFilter =
categories?.Any() == true
? Builders<Bookmark>.Filter.All(x => x.Categories, categories)
: empty;
var source = await repository
.SearchFor(Builders<Bookmark>.Filter.And(titleFilter, categoryFilter))
.ToListAsync();
return mapper.Map<List<VmBookmark>>(source);
}
[Cache(ExpirationSeconds = 30 * ExpirationTime.Day)]
public async Task<List<string>> SearchAsync(
string? title = null,
IReadOnlyCollection<string>? categories = null
)
{
if (string.IsNullOrEmpty(title))
{
return [];
}
var empty = Builders<Bookmark>.Filter.Empty;
var regexTitle = Regex.Escape(title);
var titleFilter = Builders<Bookmark>.Filter.Regex(
x => x.Title,
new BsonRegularExpression(regexTitle, "i")
);
var categoryFilter = categories is { Count: > 0 }
? Builders<Bookmark>.Filter.All(x => x.Categories, categories)
: empty;
return await repository
.SearchFor(Builders<Bookmark>.Filter.And(titleFilter, categoryFilter))
.Limit(10)
.Project(x => x.Title)
.ToListAsync();
}
[Cache(ExpirationSeconds = 30 * ExpirationTime.Day)]
public async Task<VmBookmark?> GetBookmarkAsync(string id)
{
var entity = await repository.TryGetAsync(id);
return entity == null ? null : mapper.Map<VmBookmark>(entity);
}
[Cache(ExpirationSeconds = 30 * ExpirationTime.Day)]
public async Task<List<string>> GetCategoriesAsync()
{
return await repository
.MongodbQueryable.SelectMany(x => x.Categories)
.GroupBy(x => x)
.Select(x => x.Key)
.OrderByDescending(x => x)
.ToListAsync();
}
[InvalidateCache(
Methods = [
nameof(GetBookmarksAsync),
nameof(SearchAsync),
nameof(GetBookmarkAsync),
nameof(GetCategoriesAsync),
]
)]
public async Task CreateBookmarkAsync(VmCreateBookmark bookmark)
{
var entity = mapper.Map<Bookmark>(bookmark);
if (bookmark.UploadIcon != null && !string.IsNullOrEmpty(entity.Icon))
{
var iconUrl = await bookmark.UploadIcon(entity.Icon);
if (!string.IsNullOrEmpty(iconUrl))
{
entity.Icon = iconUrl;
}
}
if (bookmark.UploadImage != null && !string.IsNullOrEmpty(entity.Image))
{
var imageUrl = await bookmark.UploadImage(entity.Image);
if (!string.IsNullOrEmpty(imageUrl))
{
entity.Image = imageUrl;
}
}
await repository.InsertAsync(entity);
}
[InvalidateCache(
Methods = [
nameof(GetBookmarksAsync),
nameof(SearchAsync),
nameof(GetBookmarkAsync),
nameof(GetCategoriesAsync),
]
)]
public async Task UpdateAsync(VmUpdateBookmark bookmark)
{
var entity = await repository.TryGetAsync(bookmark.Id);
if (entity == null)
{
return;
}
if (bookmark.Icon != entity.Icon)
{
if (!string.IsNullOrEmpty(entity.Icon) && bookmark.DeleteIcon != null)
{
await bookmark.DeleteIcon(entity.Icon);
entity.Icon = null;
}
if (!string.IsNullOrEmpty(bookmark.Icon) && bookmark.UploadIcon != null)
{
var icon = await bookmark.UploadIcon(bookmark.Icon);
entity.Icon = icon;
}
}
if (bookmark.Image != entity.Image)
{
if (!string.IsNullOrEmpty(entity.Image) && bookmark.DeleteImage != null)
{
await bookmark.DeleteImage(entity.Image);
entity.Image = null;
}
if (!string.IsNullOrEmpty(bookmark.Image) && bookmark.UploadImage != null)
{
var image = await bookmark.UploadImage(bookmark.Image);
entity.Image = image;
}
}
entity.Title = bookmark.Title ?? entity.Title;
entity.Url = bookmark.Url ?? entity.Url;
entity.Categories = bookmark.Categories;
await repository.UpdateAsync(entity);
}
[InvalidateCache(
Methods = [
nameof(GetBookmarksAsync),
nameof(SearchAsync),
nameof(GetBookmarkAsync),
nameof(GetCategoriesAsync),
]
)]
public async Task DeleteAsync(string id, Func<VmBookmark, Task> deleteSource)
{
var entity = await repository.TryGetAsync(id);
if (entity == null)
{
return;
}
await deleteSource(mapper.Map<VmBookmark>(entity));
await repository.DeleteAsync(entity.Id);
}
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
代码解释
这是一个基于 C# 的**书签服务(BookmarkService)**实现类,使用了 MongoDB 作为数据存储,并集成了缓存和对象映射功能。
整体架构
- 依赖注入:通过主构造函数注入
IRepository<Bookmark>和IMapper - 缓存策略:使用
[Cache]和[InvalidateCache]特性管理缓存 - 数据访问:通过 MongoDB 驱动进行查询和操作
核心方法解析
1. GetBookmarksAsync - 获取书签列表
[Cache(ExpirationSeconds = 30 * ExpirationTime.Day)]
- 功能:根据标题和分类过滤查询书签
- 缓存:结果缓存 30 天
- 实现细节:
- 使用 MongoDB
Filter.Regex进行标题模糊搜索(不区分大小写) - 使用
Filter.All检查分类数组是否包含指定分类 - 返回映射后的 ViewModel 列表
- 使用 MongoDB
2. SearchAsync - 搜索建议
- 功能:提供搜索自动完成功能
- 限制:最多返回 10 条结果
- 安全性:使用
Regex.Escape防止正则注入 - 返回:仅返回书签标题列表
3. GetBookmarkAsync - 获取单个书签
- 功能:根据 ID 获取书签详情
- 安全处理:实体不存在时返回
null
4. GetCategoriesAsync - 获取所有分类
- 功能:提取所有唯一分类
- 实现:
SelectMany展平所有书签的分类数组GroupBy去重- 降序排列
数据修改方法
5. CreateBookmarkAsync - 创建书签
[InvalidateCache(Methods = [...])]
- 缓存失效:创建后清除相关查询方法的缓存
- 文件上传处理:
- 支持图标(Icon)和图片(Image)上传
- 委托方式处理文件上传逻辑(
UploadIcon/UploadImage) - 上传成功后更新实体 URL
6. UpdateAsync - 更新书签
- 智能更新:仅更新变化的字段
- 文件管理:
- 检测图标/图片是否变化
- 删除旧文件(通过委托
DeleteIcon/DeleteImage) - 上传新文件
- 部分更新:使用
??运算符保留未修改字段
7. DeleteAsync - 删除书签
- 回调机制:通过
deleteSource委托执行额外清理逻辑 - 安全删除:先检查实体存在性
设计模式与特性
| 特性 | 说明 |
|---|---|
| 仓储模式 | 通过 IRepository 抽象数据访问 |
| 对象映射 | 使用 AutoMapper 等工具自动转换实体 |
| 缓存层 | AOP 方式管理缓存(读取缓存/修改失效) |
| 委托注入 | 文件操作通过委托实现解耦 |
| MongoDB 特性 | 使用 Builder 模式构建查询过滤器 |
优点
✅ 清晰的关注点分离
✅ 自动缓存管理提升性能
✅ 灵活的文件处理机制
✅ 类型安全的查询构建
潜在改进点
⚠️ 错误处理较少(文件上传失败等场景)
⚠️ 缓存过期时间硬编码(建议配置化)
⚠️ UpdateAsync 方法较长,可拆分为私有方法
AI 正在分析代码…
评论加载中...