using Dpz.Core.Public.Entity.Option;
using Dpz.Core.Public.ViewModel.Option;

namespace Dpz.Core.Service.RepositoryServiceImpl;

public class AppOptionService(IRepository<AppOption> repository, IMapper mapper) : 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";

    [Cache(ExpirationSeconds = 30 * ExpirationTime.Day)]
    public async Task<IList<VmFriends>> GetFriendsAsync()
    {
        var friends = await repository
            .SearchFor(x => x.OptionName == FriendOptionName)
            .ToListAsync();
        if (friends == null || friends.Count == 0)
        {
            return [];
        }

        var list = friends.Cast<Friends>().ToList();
        return mapper.Map<List<VmFriends>>(list);
    }

    [InvalidateCache(Methods = [nameof(GetFriendsAsync)])]
    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);
    }

    [InvalidateCache(Methods = [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;
        await repository.UpdateAsync(entityFriend);
    }

    public async Task<VmAppOption?> FindAsync(string id)
    {
        var entity = await repository.TryGetAsync(id);
        if (entity == null)
        {
            return null;
        }

        return mapper.Map<VmAppOption>(entity);
    }

    [InvalidateCache(Methods = [nameof(GetFooterContentAsync)])]
    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);
            await repository.UpdateAsync(x => x.Id == footer.Id, update);
            return;
        }

        var entity = new FooterContent
        {
            Content = content,
            CreateTime = DateTime.Now,
            LastUpdateTime = DateTime.Now,
            OptionName = FooterContentOptionName,
        };
        await repository.InsertAsync(entity);
    }

    [Cache(ExpirationSeconds = 30 * ExpirationTime.Day)]
    public async Task<string> GetFooterContentAsync()
    {
        var footer = await repository
            .SearchFor(x => x.OptionName == FooterContentOptionName)
            .FirstOrDefaultAsync();
        return footer is FooterContent entity ? entity.Content ?? "" : "";
    }

    [Cache(ExpirationSeconds = 30 * ExpirationTime.Day)]
    public async Task<string> GetRobotsAsync()
    {
        var robots = await repository
            .SearchFor(x => x.OptionName == RobotsOptionName)
            .SingleOrDefaultAsync();
        return robots is RobotsOption entity ? entity.Content : "";
    }

    [InvalidateCache(Methods = [nameof(GetRobotsAsync)])]
    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);
            await repository.UpdateAsync(x => x.Id == robots.Id, update);
            return;
        }

        var entity = new RobotsOption
        {
            Content = content,
            CreateTime = DateTime.Now,
            LastUpdateTime = DateTime.Now,
            OptionName = RobotsOptionName,
        };
        await repository.InsertAsync(entity);
    }

    [InvalidateCache(Methods = [nameof(GetFriendsAsync)])]
    public async Task DeleteFriendAsync(string id)
    {
        if (ObjectId.TryParse(id, out var oid))
        {
            await repository.DeleteAsync(x => x.OptionName == FriendOptionName && x.Id == oid);
        }
    }
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

代码解释

这是一个 应用选项服务类 (AppOptionService),用于管理应用程序的各种配置选项,如友情链接、页脚内容和 robots.txt 文件。

核心功能

1. 类结构

public class AppOptionService(IRepository<AppOption> repository, IMapper mapper) : IAppOptionService
  • 使用 主构造函数 (C# 12 特性) 注入依赖
  • repository: 数据仓储,用于数据库操作
  • mapper: 对象映射器 (AutoMapper),用于实体和视图模型之间的转换

2. 常量定义

private const string FriendOptionName = "friends";        // 友情链接标识
private const string FooterContentOptionName = "footer";  // 页脚内容标识
private const string RobotsOptionName = "robots.txt";     // robots.txt标识

主要方法

📌 友情链接管理

GetFriendsAsync() - 获取友情链接

[Cache(ExpirationSeconds = 30 * ExpirationTime.Day)]
  • 使用 缓存 (30天过期)
  • 查询所有 OptionName == "friends" 的记录
  • 将实体转换为 VmFriends 视图模型返回

AddFriendAsync() - 添加友情链接

  • 使用 [InvalidateCache] 特性使 GetFriendsAsync 的缓存失效
  • 映射视图模型到实体,设置创建/更新时间
  • 插入数据库

EditFriendAsync() - 编辑友情链接

  • 验证 ID 格式 (MongoDB ObjectId)
  • 查找并更新实体的各个属性 (头像、描述、链接、名称)
  • 更新最后修改时间

DeleteFriendAsync() - 删除友情链接

  • 验证 ObjectId 格式
  • OptionNameId 删除记录

📌 页脚内容管理

GetFooterContentAsync() - 获取页脚内容

  • 30天缓存
  • 查询 OptionName == "footer" 的记录
  • 类型转换为 FooterContent 并返回内容

SaveFooterContentAsync() - 保存页脚内容

  • 使缓存失效
  • 如果记录存在:使用 MongoDB BSON 更新文档 直接更新字段
  • 如果不存在:创建新记录

📌 Robots.txt 管理

GetRobotsAsync() - 获取 robots.txt

  • 30天缓存
  • 查询并返回 RobotsOption 内容

SaveRobotsAsync() - 保存 robots.txt

  • 逻辑与 SaveFooterContentAsync 类似
  • 使用 BSON 更新或插入新记录

📌 通用方法

FindAsync() - 查找单个选项

  • 根据 ID 查找任意 AppOption
  • 返回映射后的 VmAppOption 视图模型

技术特点

1. 缓存策略

  • 使用 AOP 缓存特性 ([Cache][InvalidateCache])
  • 读操作缓存 30 天
  • 写操作自动清除相关缓存

2. MongoDB 特性

  • 使用 ObjectId 作为主键
  • 使用 BsonDocument 构建原生更新语句:
var set = new BsonDocument
{
    { "$set", new BsonDocument { /* 字段更新 */ } }
};

3. 数据一致性

  • 所有修改操作都更新 LastUpdateTime
  • 新增操作同时设置 CreateTimeLastUpdateTime

4. 类型安全

  • 使用模式匹配检查实体类型 (is Friends, is FooterContent)
  • 使用 ObjectId.TryParse 验证 ID 格式

应用场景

这个服务适用于 CMS 系统博客系统,管理如下配置:

  • 友情链接列表
  • 网站页脚自定义内容
  • SEO 相关的 robots.txt 文件
评论加载中...