using Dpz.Core.Public.Entity.Option;
using Dpz.Core.Public.ViewModel.Option;
namespace Dpz.Core.Service.RepositoryServiceImpl;
public class AppOptionService(
IRepository<AppOption> repository,
IFusionCache fusionCache,
IMapper mapper
) : AbstractCacheService(fusionCache), IAppOptionService
{
/// <summary>
/// 友情链接 Option name
/// </summary>
private const string FriendOptionName = "friends";
/// <summary>
/// 页脚内容 Option name
/// </summary>
private const string FooterContentOptionName = "footer";
/// <summary>
/// robots.txt Option name
/// </summary>
private const string RobotsOptionName = "robots.txt";
public async Task<IList<VmFriends>> GetFriendsAsync()
{
return await GetOrSetCacheAsync<List<VmFriends>>(
nameof(GetFriendsAsync),
async (_, cancellationToken) =>
{
var friends = await repository
.SearchFor(x => x.OptionName == FriendOptionName)
.ToListAsync(cancellationToken);
if (friends == null || friends.Count == 0)
{
return [];
}
var list = friends.Cast<Friends>().ToList();
return mapper.Map<List<VmFriends>>(list);
}
);
}
public async Task AddFriendAsync(VmFriends friend)
{
var entity = mapper.Map<Friends>(friend);
entity.CreateTime = DateTime.Now;
entity.LastUpdateTime = DateTime.Now;
entity.OptionName = FriendOptionName;
await repository.InsertAsync(entity);
await RemoveByMethodAsync(nameof(GetFriendsAsync));
}
public async Task EditFriendAsync(VmFriends? friend)
{
if (string.IsNullOrEmpty(friend?.Id) || !ObjectId.TryParse(friend.Id, out var oid))
{
return;
}
var entity = await repository.FindAsync(oid);
if (entity == null || entity is not Friends entityFriend)
{
return;
}
entityFriend.Avatar = friend.Avatar;
entityFriend.Description = friend.Description;
entityFriend.Link = friend.Link;
entityFriend.Name = friend.Name;
entityFriend.LastUpdateTime = DateTime.Now;
var updateResult = await repository.UpdateAsync(entityFriend);
if (updateResult is { IsAcknowledged: true, ModifiedCount: > 0 })
{
await RemoveByMethodAsync(nameof(GetFriendsAsync));
}
}
public async Task<VmAppOption?> FindAsync(string id)
{
var entity = await repository.TryGetAsync(id);
if (entity == null)
{
return null;
}
return mapper.Map<VmAppOption>(entity);
}
public async Task SaveFooterContentAsync(string content)
{
var footer = await repository
.SearchFor(x => x.OptionName == FooterContentOptionName)
.FirstOrDefaultAsync();
if (footer != null)
{
var set = new BsonDocument
{
{
"$set",
new BsonDocument
{
{ nameof(FooterContent.Content), new BsonString(content) },
{ nameof(FooterContent.LastUpdateTime), new BsonDateTime(DateTime.Now) },
}
},
};
UpdateDefinition<AppOption> update = new BsonDocumentUpdateDefinition<AppOption>(set);
var updateResult = await repository.UpdateAsync(x => x.Id == footer.Id, update);
if (updateResult is { IsAcknowledged: true, ModifiedCount: > 0 })
{
await RemoveByMethodAsync(nameof(GetFooterContentAsync));
}
return;
}
var entity = new FooterContent
{
Content = content,
CreateTime = DateTime.Now,
LastUpdateTime = DateTime.Now,
OptionName = FooterContentOptionName,
};
await repository.InsertAsync(entity);
await RemoveByMethodAsync(nameof(GetFooterContentAsync));
}
public async Task<string> GetFooterContentAsync()
{
return await GetOrSetCacheAsync<string>(
nameof(GetFooterContentAsync),
async (_, cancellationToken) =>
{
var footer = await repository
.SearchFor(x => x.OptionName == FooterContentOptionName)
.FirstOrDefaultAsync(cancellationToken);
return footer is FooterContent entity ? entity.Content ?? "" : "";
}
);
}
public Task<string> GetRobotsAsync()
{
return GetOrSetCacheAsync<string>(
nameof(GetRobotsAsync),
async (_, cancellationToken) =>
{
var robots = await repository
.SearchFor(x => x.OptionName == RobotsOptionName)
.SingleOrDefaultAsync(cancellationToken);
return robots is RobotsOption entity ? entity.Content : "";
}
);
}
public async Task SaveRobotsAsync(string content)
{
var robots = await repository
.SearchFor(x => x.OptionName == RobotsOptionName)
.FirstOrDefaultAsync();
if (robots != null)
{
var set = new BsonDocument
{
{
"$set",
new BsonDocument
{
{ nameof(RobotsOption.Content), new BsonString(content) },
{ nameof(RobotsOption.LastUpdateTime), new BsonDateTime(DateTime.Now) },
}
},
};
UpdateDefinition<AppOption> update = new BsonDocumentUpdateDefinition<AppOption>(set);
var updateResult = await repository.UpdateAsync(x => x.Id == robots.Id, update);
if (updateResult is { IsAcknowledged: true, ModifiedCount: > 0 })
{
await RemoveByMethodAsync(nameof(GetRobotsAsync));
}
return;
}
var entity = new RobotsOption
{
Content = content,
CreateTime = DateTime.Now,
LastUpdateTime = DateTime.Now,
OptionName = RobotsOptionName,
};
await repository.InsertAsync(entity);
await RemoveByMethodAsync(nameof(GetRobotsAsync));
}
public async Task DeleteFriendAsync(string id)
{
if (ObjectId.TryParse(id, out var oid))
{
var deleteResult = await repository.DeleteAsync(x =>
x.OptionName == FriendOptionName && x.Id == oid
);
if (deleteResult is { IsAcknowledged: true, DeletedCount: > 0 })
{
await RemoveCacheAsync(nameof(GetFriendsAsync));
}
}
}
protected override TimeSpan CacheDefaultExpiration => TimeSpan.FromDays(30);
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
这段 C# 代码实现了一个应用配置/选项(AppOption)相关的服务类 AppOptionService,负责对若干类型的应用选项(例如友情链接、页脚内容、robots.txt)进行增删改查,同时结合缓存和持久层(Repository)以提高性能。下面分项说明其职责、每个方法的功能以及一些实现细节和注意点。
总体结构与依赖
- 继承自 AbstractCacheService(提供缓存相关的辅助方法,如 GetOrSetCacheAsync、RemoveByMethodAsync、RemoveCacheAsync 等)。
- 通过构造函数注入三个依赖:
- IRepository
repository:持久层仓储(基于 MongoDB 的实体映射,可进行查询、更新、插入、删除等)。 - IFusionCache fusionCache:缓存(由基类使用)。
- IMapper mapper:对象映射(AutoMapper),用于在实体与视图模型之间转换。
- IRepository
- 常量定义了三类 option 的 OptionName:FriendOptionName = "friends",FooterContentOptionName = "footer",RobotsOptionName = "robots.txt"。
- 提供的主要功能领域:友情链接管理、页脚内容读写、robots.txt 读写及通用的按 id 查找。
方法说明
GetFriendsAsync()
- 从缓存中读取/设置友情链接列表(键使用方法名)。
- 若缓存未命中,则通过 repository.SearchFor(x => x.OptionName == "friends") 查询所有相关 AppOption。
- 将结果转换为 Friends 实体列表,再用 mapper 映射为 List
返回。 - 注意:代码中写了 return []; 这在 C# 中是语法错误(应该返回 new List
() 或 Array.Empty ()),应修正。
AddFriendAsync(VmFriends friend)
- 将传入的视图模型映射为 Friends 实体,设置 CreateTime、LastUpdateTime、OptionName,然后插入到仓储。
- 插入成功后调用 RemoveByMethodAsync(nameof(GetFriendsAsync)) 清除/失效对应缓存,使下次读取能拿到最新数据。
EditFriendAsync(VmFriends? friend)
- 先校验传入对象的 Id 是否有效(非空且能解析为 ObjectId)。
- 从仓储中 FindAsync(oid) 获取实体并确保为 Friends 类型。
- 更新实体的字段(Avatar、Description、Link、Name、LastUpdateTime),并执行 UpdateAsync。
- 若更新被确认并实际修改(IsAcknowledged && ModifiedCount > 0),则清除 GetFriendsAsync 的缓存。
FindAsync(string id)
- 调用 repository.TryGetAsync(id)(按 id 获取单个 AppOption 实体),若存在则用 mapper 映射为 VmAppOption 返回,否则返回 null。
- 用于按 id 获取单个选项数据(通用接口)。
SaveFooterContentAsync(string content)
- 查询是否已有 OptionName == "footer" 的记录(FirstOrDefaultAsync)。
- 若存在:构造 BSON 局部更新($set)只更新 Content 与 LastUpdateTime,使用 repository.UpdateAsync(x => x.Id == footer.Id, update) 执行部分更新;更新成功则清除 GetFooterContentAsync 缓存。
- 若不存在:构造新的 FooterContent 实体(含 CreateTime/LastUpdateTime/OptionName)插入,并清除对应缓存。
- 使用 BsonDocumentUpdateDefinition
来做部分更新(减少覆盖整个文档)。
GetFooterContentAsync()
- 通过缓存读取/设置页脚内容(键为方法名)。
- 若 DB 中存在 FooterContent 实体,则返回其 Content,否则返回空字符串。
GetRobotsAsync()
- 与 GetFooterContentAsync 类似,但针对 OptionName == "robots.txt" 并期望实体类型为 RobotsOption。
- 注意这里使用 SingleOrDefaultAsync:如果数据库中存在多条 robots 条目,会抛出异常(SingleOrDefault 与 FirstOrDefault 的行为不同)。
SaveRobotsAsync(string content)
- 与 SaveFooterContentAsync 类似:先查找是否存在 robots 记录,存在则用局部更新($set 更新 Content 与 LastUpdateTime),并在成功时清除 GetRobotsAsync 缓存;不存在则插入新 RobotsOption 实体并清除缓存。
DeleteFriendAsync(string id)
- 校验 id 为合法 ObjectId 后,调用 repository.DeleteAsync(x => x.OptionName == "friends" && x.Id == oid) 删除指定友情链接条目。
- 若删除成功(IsAcknowledged && DeletedCount > 0),则调用 RemoveCacheAsync(nameof(GetFriendsAsync)) 清除缓存(注意此处用的是 RemoveCacheAsync 而非 RemoveByMethodAsync,与其他方法略有不一致)。
缓存策略
- 缓存键均采用方法名(nameof(...)),基类的 GetOrSetCacheAsync 封装了缓存读取与回源逻辑。
- 重写了 CacheDefaultExpiration => TimeSpan.FromDays(30),默认缓存过期时间为 30 天(示例值,根据实际需求可调整)。
实体与类型
- AppOption 似乎是一个基类,具体有派生类型:
- Friends(保存单个友情链接条目)
- FooterContent(保存页脚内容)
- RobotsOption(保存 robots.txt 内容)
- 视图模型有对应类型:VmFriends、VmAppOption 等,使用 IMapper 做实体与视图模型之间转换。
实现细节与潜在问题/改进建议
- return [] 是语法错误,应改为 new List
() 或 Array.Empty ()。 - DateTime.Now 在分布式/跨时区场景可能不够可靠,建议使用 DateTime.UtcNow 并在显示层或需要本地时间时再转换。
- GetRobotsAsync 使用 SingleOrDefaultAsync,如果数据库有多条 robots 记录会抛异常;如果允许存在多条或不确定数量,建议改用 FirstOrDefaultAsync 或在插入时保证唯一约束。
- SaveFooterContentAsync/SaveRobotsAsync 使用手工构造的 BsonDocument 更新(部分更新),这很好可以避免覆盖其它字段。但注意字段名需与实体成员一致。
- 删除成功后使用了 RemoveCacheAsync,而其它地方使用 RemoveByMethodAsync(语义/实现可能不同),最好统一缓存失效调用方式以免混淆。
- 对 repository 的异常处理较少(例如 UpdateAsync/DeleteAsync 可能抛异常或返回异常结果),可增加日志与更细致的错误处理。
- 如果 repository.SearchFor 返回 IQueryable 并延迟执行,需要传入的 CancellationToken 都被正确传递(示例中大多数异步方法传递了 cancellationToken,少数 Save* 方法没有传 cancellationToken)。
总结 该服务类的主要职责是管理应用配置项(主要是友情链接、页脚内容、robots),封装了对 MongoDB 仓储的查询/更新/插入/删除逻辑,并结合缓存机制避免频繁访问数据库。代码总体结构清晰,职责单一;但存在小的实现细节需修正(如返回空列表的语法错误、时间使用、缓存失效调用不一致与 SingleOrDefault 的使用等)。
评论加载中...