namespace Dpz.Core.Service.Mediator.Features.Code.Commands;

/// <summary>
/// 处理代码文档兼容:将历史备注和 AI 分析结果回填到文件树节点。
/// </summary>
public class CodeCompatibleHandler(
    IRepository<CodeFileSystemEntry> codeFileSystemEntryRepository,
    IRepository<CodeNote> codeNoteRepository
) : IRequestHandler<CodeCompatibleRequest>
{
    /// <summary>
    /// 按请求参数执行增量或全量兼容流程。
    /// </summary>
    public async ValueTask<Unit> Handle(
        CodeCompatibleRequest request,
        CancellationToken cancellationToken
    )
    {
        if (request.NewEntries is { Count: > 0 })
        {
            await HandleIncrementalAsync(request.NewEntries, cancellationToken);
            return Unit.Value;
        }

        await HandleFullAsync(cancellationToken);
        return Unit.Value;
    }

    private async Task HandleIncrementalAsync(
        IReadOnlyCollection<CodeCompatibleEntry> newEntries,
        CancellationToken cancellationToken
    )
    {
        var pathGroups = newEntries
            .Select(x => BuildPathString(x.ParentPathSegments))
            .Where(x => !string.IsNullOrWhiteSpace(x))
            .Distinct(StringComparer.OrdinalIgnoreCase)
            .ToList();
        if (pathGroups.Count == 0)
        {
            return;
        }

        var noteFilter = Builders<CodeNote>.Filter.In(x => x.Path, pathGroups);
        var notes = await codeNoteRepository.SearchFor(noteFilter).ToListAsync(cancellationToken);
        if (notes.Count == 0)
        {
            return;
        }

        var noteMap = notes.ToDictionary(
            x => BuildNoteKey(x.Path, x.Name),
            StringComparer.OrdinalIgnoreCase
        );

        var updateList = new List<CodeFileSystemEntry>();

        foreach (var entry in newEntries)
        {
            var key = BuildNoteKey(BuildPathString(entry.ParentPathSegments), entry.Name);
            if (!noteMap.TryGetValue(key, out var note))
            {
                continue;
            }

            var entryPathSegments = BuildPathSegments(entry.ParentPathSegments, entry.Name);
            var entryFilter = Builders<CodeFileSystemEntry>.Filter.Eq(
                x => x.PathSegments,
                entryPathSegments
            );
            var updatedEntry = await codeFileSystemEntryRepository
                .SearchFor(entryFilter)
                .FirstOrDefaultAsync(cancellationToken);
            if (updatedEntry == null)
            {
                continue;
            }

            var changed = false;
            if (
                string.IsNullOrWhiteSpace(updatedEntry.Description)
                && !string.IsNullOrWhiteSpace(note.Note)
            )
            {
                updatedEntry.Description = note.Note;
                changed = true;
            }

            if (
                string.IsNullOrWhiteSpace(updatedEntry.AiAnalyzeResult)
                && !string.IsNullOrWhiteSpace(note.AiAnalyzeResult)
            )
            {
                updatedEntry.AiAnalyzeResult = note.AiAnalyzeResult;
                changed = true;
            }

            if (changed)
            {
                updateList.Add(updatedEntry);
            }
        }

        if (updateList.Count > 0)
        {
            await codeFileSystemEntryRepository.UpdateAsync(updateList, cancellationToken);
        }
    }

    private async Task HandleFullAsync(CancellationToken cancellationToken)
    {
        var notes = await codeNoteRepository.SearchFor(x => true).ToListAsync(cancellationToken);
        if (notes.Count == 0)
        {
            return;
        }

        var entries = await codeFileSystemEntryRepository
            .SearchFor(x => true)
            .ToListAsync(cancellationToken);
        if (entries.Count == 0)
        {
            return;
        }

        var entryMap = entries.ToDictionary(
            x => BuildPathKey(x.PathSegments),
            StringComparer.OrdinalIgnoreCase
        );

        var updateList = new List<CodeFileSystemEntry>();

        foreach (var note in notes)
        {
            var pathSegments = BuildPathSegments(note.Path, note.Name);
            if (pathSegments.Count == 0)
            {
                continue;
            }

            var key = BuildPathKey(pathSegments);
            if (!entryMap.TryGetValue(key, out var entry))
            {
                continue;
            }

            var changed = false;
            if (
                string.IsNullOrWhiteSpace(entry.Description)
                && !string.IsNullOrWhiteSpace(note.Note)
            )
            {
                entry.Description = note.Note;
                changed = true;
            }

            if (
                string.IsNullOrWhiteSpace(entry.AiAnalyzeResult)
                && !string.IsNullOrWhiteSpace(note.AiAnalyzeResult)
            )
            {
                entry.AiAnalyzeResult = note.AiAnalyzeResult;
                changed = true;
            }

            if (changed)
            {
                updateList.Add(entry);
            }
        }

        if (updateList.Count > 0)
        {
            await codeFileSystemEntryRepository.UpdateAsync(updateList, cancellationToken);
        }
    }

    private static List<string> BuildPathSegments(string? path, string name)
    {
        var segments = new List<string>();
        if (!string.IsNullOrWhiteSpace(path))
        {
            segments.AddRange(
                path.Split(
                    '/',
                    StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries
                )
            );
        }

        if (!string.IsNullOrWhiteSpace(name))
        {
            segments.Add(name);
        }

        return segments;
    }

    private static string BuildPathKey(IEnumerable<string> segments)
    {
        return string.Join("/", segments);
    }

    private static string BuildPathString(IEnumerable<string> segments)
    {
        return string.Join("/", segments.Where(x => !string.IsNullOrWhiteSpace(x)));
    }

    private static string BuildNoteKey(string path, string name)
    {
        return string.Join("/", new[] { path, name }.Where(x => !string.IsNullOrWhiteSpace(x)));
    }

    private static List<string> BuildPathSegments(
        IEnumerable<string> parentPathSegments,
        string name
    )
    {
        var segments = parentPathSegments.Where(x => !string.IsNullOrWhiteSpace(x)).ToList();
        if (!string.IsNullOrWhiteSpace(name))
        {
            segments.Add(name);
        }

        return segments;
    }
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

这是一个用于处理代码文档兼容性的命令处理器,主要功能是将历史备注和AI分析结果同步到文件系统树节点中。让我详细解释一下这个代码:

类的基本结构

public class CodeCompatibleHandler : IRequestHandler<CodeCompatibleRequest>

这是一个实现了MediatR模式的命令处理器,用于处理CodeCompatibleRequest请求。

构造函数依赖注入

public CodeCompatibleHandler(
    IRepository<CodeFileSystemEntry> codeFileSystemEntryRepository,
    IRepository<CodeNote> codeNoteRepository
)

注入了两个仓储:

  • codeFileSystemEntryRepository:管理文件系统条目
  • codeNoteRepository:管理代码备注

主要处理逻辑

Handle方法

根据请求参数决定执行增量处理还是全量处理:

  • 如果request.NewEntries不为空,执行增量处理
  • 否则执行全量处理

增量处理(HandleIncrementalAsync)

  1. 路径分组:从新条目中提取并去重父路径
  2. 查找匹配的备注:根据路径查找对应的CodeNote记录
  3. 构建备注映射:创建key-value映射,key为路径+名称的组合
  4. 更新文件系统条目
    • 遍历新条目,查找匹配的备注
    • 如果文件系统条目的描述为空且备注不为空,则更新描述
    • 如果文件系统条目的AI分析结果为空且备注的AI分析结果不为空,则更新
  5. 批量保存:将所有修改的条目批量更新到数据库

全量处理(HandleFullAsync)

  1. 加载所有数据:获取所有CodeNote和CodeFileSystemEntry记录
  2. 构建条目映射:以路径为key创建文件系统条目映射
  3. 同步数据
    • 遍历所有备注
    • 查找对应的文件系统条目
    • 按相同规则更新描述和AI分析结果
  4. 批量保存:更新修改的条目

辅助方法

路径处理方法

  • BuildPathSegments:将路径字符串和名称组合成路径段列表
  • BuildPathKey:将路径段连接成完整路径字符串
  • BuildPathString:过滤空值后连接路径段
  • BuildNoteKey:构建备注的唯一键

核心业务逻辑

这个处理器的主要目的是数据同步

  1. 确保文件系统条目具有对应的描述信息(从CodeNote.Note字段同步)
  2. 确保文件系统条目具有AI分析结果(从CodeNote.AiAnalyzeResult字段同步)
  3. 采用"只填充空值"的策略,不会覆盖已有数据

设计特点

  1. 增量与全量支持:可以针对特定新增条目进行增量处理,也可以进行全量同步
  2. 安全更新:只在目标字段为空时才进行更新,避免数据覆盖
  3. 批量操作:收集所有需要更新的条目后批量执行,提高性能
  4. 异步处理:全程使用异步操作,支持取消令牌

这个设计很适合在代码分析系统中,将历史的备注数据和AI分析结果迁移到新的文件系统结构中。

评论加载中...