using Dpz.Core.Public.ViewModel;

namespace Dpz.Core.Service.RepositoryServiceImpl;

public class CodeNoteService(
    IRepository<CodeNote> repository,
    IMapper mapper,
    ILogger<CodeNoteService> logger,
    IConfiguration configuration
) : ICodeNoteService
{
    public async Task<string> SaveNoteAsync(string[]? path, string name, string? note)
    {
        var codeNote = await BuildFluent(path, name, out var pathStr).FirstOrDefaultAsync();
        if (codeNote == null)
        {
            var entity = new CodeNote
            {
                CreateTime = DateTime.Now,
                LastUpdateTime = DateTime.Now,
                Name = name,
                Note = note,
                Path = pathStr,
            };
            await repository.InsertAsync(entity);
            return entity.Id.ToString();
        }

        codeNote.Note = note;
        codeNote.LastUpdateTime = DateTime.Now;
        await repository.UpdateAsync(codeNote);
        return codeNote.Id.ToString();
    }

    public async Task<VmCodeNote?> FindAsync(string[]? path, string? name)
    {
        if (string.IsNullOrWhiteSpace(name))
        {
            return null;
        }
        var codeNote = await BuildFluent(path, name, out _).FirstOrDefaultAsync();

        if (codeNote == null)
        {
            return null;
        }

        return mapper.Map<VmCodeNote>(codeNote);
    }

    private static string GetPathStr(string[]? path)
    {
        return path is not null && path.Length > 0 ? string.Join("/", path) : "";
    }

    private static FilterDefinition<CodeNote> BuildFilter(
        string[]? path,
        string? name,
        out string pathStr
    )
    {
        pathStr = GetPathStr(path);
        return Builders<CodeNote>.Filter.And(
            Builders<CodeNote>.Filter.Eq(x => x.Path, pathStr),
            Builders<CodeNote>.Filter.Eq(x => x.Name, name)
        );
    }

    private IFindFluent<CodeNote, CodeNote> BuildFluent(
        string[]? path,
        string name,
        out string pathStr
    )
    {
        var filter = BuildFilter(path, name, out pathStr);
        return repository.SearchFor(
            filter,
            new FindOptions
            {
                Collation = new Collation(
                    locale: "en",
                    strength: new Optional<CollationStrength?>(CollationStrength.Secondary)
                ),
            }
        );
    }

    public async Task SaveAiAnalyzeResultAsync(string[]? path, string name, string analyzeResult)
    {
        var codeNote = await BuildFluent(path, name, out var pathStr).FirstOrDefaultAsync();
        if (codeNote == null)
        {
            var entity = new CodeNote
            {
                CreateTime = DateTime.Now,
                LastUpdateTime = DateTime.Now,
                Name = name,
                AiAnalyzeResult = analyzeResult,
                Path = pathStr,
            };
            await repository.InsertAsync(entity);
            return;
        }

        codeNote.AiAnalyzeResult = analyzeResult;
        codeNote.LastUpdateTime = DateTime.Now;
        await repository.UpdateAsync(codeNote);
        return;
    }

    public async Task<bool> ShouldAnalyzeAsync(
        string[]? path,
        string? name,
        CodeContainer? codeContainer
    )
    {
        var useAiAnalyze = configuration.GetValue("CodeUseAIAnalyze", false);
        if (!useAiAnalyze)
        {
            return false;
        }

        if (
            codeContainer is not { Language: "csharp" }
            || string.IsNullOrWhiteSpace(codeContainer.CodeContent)
            || string.IsNullOrWhiteSpace(name)
        )
        {
            return false;
        }

        var codeNote = await FindAsync(path, name);
        if (string.IsNullOrWhiteSpace(codeContainer.AiAnalyzeResult))
        {
            return true;
        }

        if (codeNote == null)
        {
            return false;
        }

        return DateTime.Now - codeNote.LastUpdateTime > TimeSpan.FromDays(30);
    }

    public async Task<List<VmCodeNote>> GetNoteAsync(string[]? path)
    {
#if DEBUG
        var stopwatch = new Stopwatch();
        stopwatch.Start();
#endif
        var pathStr = GetPathStr(path);
        var filter = Builders<CodeNote>.Filter.Eq(x => x.Path, pathStr);
        var list = await repository
            .SearchFor(
                filter,
                new FindOptions
                {
                    Collation = new Collation(
                        locale: "en",
                        strength: new Optional<CollationStrength?>(CollationStrength.Secondary)
                    ),
                }
            )
            .ToListAsync();
#if DEBUG
        stopwatch.Stop();
        var queryTime = stopwatch.Elapsed;
        logger.LogInformation("查询耗时:{QueryTime} ms", queryTime.TotalMilliseconds);
#endif
        return mapper.Map<List<VmCodeNote>>(list);
    }

    public async Task<List<VmCodeNote>> GetAllNoteAsync()
    {
        var list = await repository.SearchFor(x => true).ToListAsync();
        return mapper.Map<List<VmCodeNote>>(list);
    }

    public async Task DeleteAsync(string[]? path, string name)
    {
        var filter = BuildFilter(path, name, out _);
        await repository.DeleteAsync(filter);
    }
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

这段代码定义了一个名为 CodeNoteService 的服务类(实现了 ICodeNoteService),主要用于在后端持久层(通过 IRepository)对“代码备注/分析结果”实体进行 CRUD 与查询,并结合 IMapper 做 DTO(VmCodeNote)映射、ILogger 做日志、IConfiguration 读取配置。下面分块解释各个要点与方法行为:

总体说明

  • 实体:CodeNote(推断包含 Id、CreateTime、LastUpdateTime、Name、Note、Path、AiAnalyzeResult 等字段)。
  • 存储:通过泛型仓储 IRepository 操作数据库(看上去像 MongoDB:使用 Builders、FilterDefinition、IFindFluent、Collation 等)。
  • 映射:IMapper 用于将 CodeNote 映射为 VmCodeNote(返回给客户端的视图模型)。
  • 路径处理:外部传入 string[]? path,内部通过 GetPathStr 将数组用 "/" 连接成一个路径字符串(空数组/null => "")。

方法详解

  1. SaveNoteAsync(string[]? path, string name, string? note)
  • 尝试按 path + name 查找已存在的 CodeNote(使用 BuildFluent,带特定 collation)。
  • 如果没找到:构建新的 CodeNote(设置 CreateTime、LastUpdateTime、Name、Note、Path),插入数据库,返回新记录的 Id 字符串。
  • 如果找到:更新其 Note 与 LastUpdateTime,保存更新,返回已有记录 Id。
  1. FindAsync(string[]? path, string? name)
  • 如果 name 空或空白则返回 null。
  • 使用 BuildFluent 查找(path + name);找到就用 mapper 映射为 VmCodeNote 返回,否则返回 null。
  1. GetPathStr(string[]? path)
  • 私有静态方法:把 path 数组 join 为 "a/b/c" 的形式,若 null 或空则返回空字符串 ""。
  1. BuildFilter(...)(静态)
  • 构造 MongoDB 的 FilterDefinition:Path 等于 pathStr 且 Name 等于 name,用于 Delete/查找等。
  1. BuildFluent(string[]? path, string name, out string pathStr)
  • 基于 BuildFilter 构建 IFindFluent<CodeNote, CodeNote> 查询,并传入 FindOptions 指定 Collation:
    • locale = "en"
    • strength = CollationStrength.Secondary
  • 这意味着在比较时使用二级强度的排序规则(通常会忽略大小写差异,即实现 case-insensitive 比较,diacritics 处理根据 locale/strength 行为决定)。
  1. SaveAiAnalyzeResultAsync(string[]? path, string name, string analyzeResult)
  • 与 SaveNoteAsync 相似,但保存/更新的是 AiAnalyzeResult 字段(同时更新 LastUpdateTime),若不存在则插入新记录。
  1. ShouldAnalyzeAsync(string[]? path, string? name, CodeContainer? codeContainer)
  • 决定是否需要对某段代码调用 AI 分析,逻辑如下:
    • 先读取配置键 "CodeUseAIAnalyze"(默认 false),若该配置为 false 则直接返回 false(不分析)。
    • 如果 codeContainer 不存在或其 Language 不是 "csharp",或 CodeContent 为空,或 name 为空,返回 false。
    • 如果 codeContainer.AiAnalyzeResult 为空(说明当前代码还没有 AI 结果),返回 true(需要分析)。
    • 否则查找已有的 codeNote(FindAsync);如果没有对应的 codeNote,返回 false(已有 AI 结果但没有保存备注则不触发?——代码逻辑是这样)。
    • 如果有 codeNote,则判断上次更新时间距今是否超过 30 天:超过 30 天则返回 true(需要重新分析),否则 false。
  1. GetNoteAsync(string[]? path)
  • 查询指定 path 下的所有 CodeNote(Path 等于 pathStr),使用与 BuildFluent 相同的 Collation(case-insensitive),把结果列表映射为 List 返回。
  • 在 DEBUG 模式下还会测量并记录查询耗时(logger.LogInformation)。
  1. GetAllNoteAsync()
  • 返回仓库中所有 CodeNote(SearchFor(x => true)),并映射为 VmCodeNote 列表。
  1. DeleteAsync(string[]? path, string name)
  • 构造 Path+Name 的 filter,然后调用 repository.DeleteAsync 删除对应记录。

一些实现细节与注意点

  • Collation 的使用:BuildFluent 和 GetNoteAsync 在查询时都显式设置了 Collation 且 strength = Secondary,通常用于忽略大小写差异以做不区分大小写的匹配。注意插入/更新时没有显式设置 collation(只是查询时有效),插入时 Path 与 Name 的真实值(大小写)会被存储。
  • 路径字符串构造:用 "/" 连接,注意如果 path 数组中包含 "/" 字符或空字符串,存储结果可能不是预期的分层表示。
  • 时间字段:CreateTime 与 LastUpdateTime 都使用 DateTime.Now(本地时间);如果系统分布式/跨时区,考虑使用 UTC(DateTime.UtcNow)。
  • 配置开关:是否使用 AI 分析由配置键 "CodeUseAIAnalyze" 控制,默认 false(代码中 GetValue("CodeUseAIAnalyze", false))。
  • 代码对空值的保护:部分方法会检查 name 或 codeContainer.CodeContent 是否为 null/空;Find/Save 等查找依据 name 与 path 精确相等(但查询时使用了 collation)。

总体上该服务封装了对 CodeNote 的常见操作(增/查/改/删)、基于配置的 AI 分析触发决策以及将数据库实体映射为视图模型返回给调用方。若需要,我可以帮你:

  • 说明某个方法的具体调用场景或接口签名;
  • 建议改进(例如使用 UTC、处理 path 的规范化、增强并发/事务处理、添加索引建议等)。
评论加载中...