using Dpz.Core.Public.ViewModel.Request;
using Dpz.Core.Public.ViewModel.Response;

namespace Dpz.Core.Service.RepositoryService;

public interface ICodeFileSystemEntryService
{
    /// <summary>
    /// 根据路径分段查找文件系统实体
    /// </summary>
    /// <param name="pathSegments">路径分段列表(null 或空表示根目录)</param>
    /// <param name="cancellationToken">取消令牌</param>
    /// <returns>匹配的响应模型,不存在则返回 null</returns>
    Task<CodeFileSystemEntryResponse?> FindByPathAsync(
        IReadOnlyCollection<string>? pathSegments,
        CancellationToken cancellationToken = default
    );

    /// <summary>
    /// 根据路径分段查找文件系统实体(绕过缓存,读取最新数据库状态)
    /// </summary>
    /// <param name="pathSegments">路径分段列表(null 或空表示根目录)</param>
    /// <param name="cancellationToken">取消令牌</param>
    /// <returns>匹配的响应模型,不存在则返回 null</returns>
    Task<CodeFileSystemEntryResponse?> FindByPathWithoutCacheAsync(
        IReadOnlyCollection<string>? pathSegments,
        CancellationToken cancellationToken = default
    );

    /// <summary>
    /// 获取指定父路径下的所有子项
    /// </summary>
    /// <param name="parentPathSegments">父路径分段列表(null 或空表示根目录)</param>
    /// <param name="cancellationToken">取消令牌</param>
    /// <returns>子项响应列表</returns>
    Task<List<CodeFileSystemEntryResponse>> GetChildrenAsync(
        IReadOnlyCollection<string>? parentPathSegments,
        CancellationToken cancellationToken = default
    );

    /// <summary>
    /// 根据关键字搜索文件/目录名称(忽略大小写)
    /// </summary>
    /// <param name="keyword">搜索关键字</param>
    /// <param name="cancellationToken">取消令牌</param>
    /// <returns>匹配的响应列表</returns>
    Task<List<CodeFileSystemEntryResponse>> SearchAsync(
        string keyword,
        CancellationToken cancellationToken = default
    );

    /// <summary>
    /// 在可预览文本文件的内容中按字面量子串搜索(忽略大小写)
    /// </summary>
    /// <param name="query">搜索词,按字面量匹配并转义正则元字符</param>
    /// <param name="maxResults">返回条数上限,默认且最大 20</param>
    /// <param name="cancellationToken">取消令牌</param>
    /// <returns>命中列表;无匹配或空查询返回空列表</returns>
    Task<List<CodeContentSearchHitResponse>> SearchContentAsync(
        string query,
        int? maxResults = null,
        CancellationToken cancellationToken = default
    );

    /// <summary>
    /// 获取分页列表,支持路径、名称、扩展名查询和多种排序
    /// </summary>
    /// <param name="request">查询请求参数</param>
    /// <param name="cancellationToken">取消令牌</param>
    /// <returns>分页列表</returns>
    Task<IPagedList<CodeFileSystemEntryListResponse>> GetPagedListAsync(
        CodeFlatRequest request,
        CancellationToken cancellationToken = default
    );

    /// <summary>
    /// 获取所有目录,支持根据路径过滤
    /// </summary>
    /// <param name="pathSegments">路径分段列表(null 或空表示获取所有目录)</param>
    /// <param name="cancellationToken">取消令牌</param>
    /// <returns>目录列表</returns>
    Task<List<string[]>> GetAllDirectoriesAsync(
        string[]? pathSegments = null,
        CancellationToken cancellationToken = default
    );

    /// <summary>
    /// 保存描述
    /// </summary>
    /// <param name="path">路径分段</param>
    /// <param name="name">文件或目录名称</param>
    /// <param name="description">描述</param>
    /// <param name="cancellationToken">取消令牌</param>
    /// <returns>是否保存成功</returns>
    Task<bool> SaveDescriptionAsync(
        string[]? path,
        string name,
        string? description,
        CancellationToken cancellationToken = default
    );

    /// <summary>
    /// 保存AI分析结果
    /// </summary>
    /// <param name="path">路径分段</param>
    /// <param name="name">文件名称</param>
    /// <param name="analyzeResult">分析结果</param>
    /// <param name="analyzedHash">源文件 hash</param>
    /// <param name="cancellationToken">取消令牌</param>
    Task SaveAiAnalyzeResultAsync(
        string[]? path,
        string name,
        string analyzeResult,
        string? analyzedHash = null,
        CancellationToken cancellationToken = default
    );

    /// <summary>
    /// 判断是否需要触发AI分析
    /// </summary>
    /// <param name="path"></param>
    /// <param name="name"></param>
    /// <param name="codeContainer"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    Task<bool> ShouldAnalyzeAsync(
        string[]? path,
        string? name,
        CodeContainer? codeContainer,
        CancellationToken cancellationToken = default
    );
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

我已获取足够的上下文(实现类、实体、响应模型、控制器与后台任务调用方),下面给出对该文件的说明报告。

文件定位与职责

ICodeFileSystemEntryService.cs 是代码仓库(源码库)文件系统实体的服务层接口契约,位于 Dpz.Core.Service/RepositoryService 接口目录。它的实体实现是 CodeFileSystemEntryService.cs(位于 RepositoryServiceImpl 实现目录),底层持久化使用的是 MongoDB(实现代码中通过 Builders<CodeFileSystemEntry>.FilterBsonRegularExpression 构造查询)。

接口围绕「把磁盘代码扫描入库后的文件/目录树」提供了一套以“路径分段数组(pathSegments)”为主键抽象的查询与写操作能力。持久化实体为 CodeFileSystemEntry(见 CodeFileSystemEntry.cs),对外返回的视图模型主要是 CodeFileSystemEntryResponse.cs,其中根目录用空的分段列表表示,每个条目记录了完整路径分段、父路径分段、是否为目录、扩展名、大小、Hash、文件内容、语言、标签、描述以及 AI 分析结果等字段。

接口方法分组与语义

接口共声明 11 个方法,按职责可分成四组:

1. 路径与目录树查询

  • FindByPathAsync:按路径分段查找实体,命中缓存(实现中走 FusionCache,默认 7 天)。
  • FindByPathWithoutCacheAsync:语义相同的“绕过缓存、直读数据库”版本,用于后台需要看到最新状态的场景(如 AI 分析任务)。
  • GetChildrenAsync:列出某父路径下的全部子项,是前端文件树/侧边栏逐级展开的核心。
  • GetAllDirectoriesAsync:返回所有目录的路径分段(可选按某路径过滤,含其全部子目录)。

2. 搜索

  • SearchAsync:按关键字模糊匹配名称(支持 */? 通配符并做 glob 语义处理)。
  • SearchContentAsync:仅在可预览文本文件的内容中做忽略大小写的字面量子串搜索,关键字先经正则元字符转义以避免 MongoDB 正则注入,默认且最多返回 20 条,命中项为 CodeContentSearchHitResponse

3. 后台管理/分页列表

  • GetPagedListAsync:接收 CodeFlatRequest,支持按路径、名称、扩展名过滤,以及按大小、创建时间、修改时间、AI 分析时间等字段排序的分页查询。

4. 元数据维护与 AI 分析触发

  • SaveDescriptionAsync:保存人工描述,返回是否成功。
  • SaveAiAnalyzeResultAsync:保存 AI 分析结论、被分析文件的源 Hash 及分析时间。
  • ShouldAnalyzeAsync:判断一个文件是否应触发 AI 分析——实现中会检查配置开关 CodeUseAIAnalyze、内容非空、代码行数 ≥ 50、非 .min 文件、语言属于 csharp/javascript/typescript,并且要求文件尚无分析结果或原 Hash 已变化(用于去重)。

关键协作方与调用链

根据代码检索,该接口被以下模块消费:

  • 网站前端控制器 CodeController.cs:浏览 code/{**path} 时先由扩展方法 BuildCodeNoteTreeAsync 组装目录树(见 CodeNoteTreeExtensions.cs),再调用 ShouldAnalyzeAsync/FindByPathAsync 决定是否向消息队列发布 AnalyzeCodeMessage;侧边栏树用 GetChildrenAsync 逐级懒加载;搜索页用 SearchAsync
  • API 控制器 CodeController.cs:提供分页列表、目录枚举等后台管理接口。
  • MCP 工具类 CodeExplorerMcpTools.cs:把接口能力暴露为 MCP 服务端工具;测试工程 CodeExplorerMcpToolsTest.cs 中实现了 Fake 版本以作替身。
  • 后台任务处理器 AnalyzeCodeHandler.cs:消费 AnalyzeCodeMessage,通过 FindByPathWithoutCacheAsync 读取最新实体并在分析完成后写回结果。

在实现类中,读写方法都会维护缓存一致性:写入(SaveDescriptionAsyncSaveAiAnalyzeResultAsync)成功后,会按路径和父路径失效对应的实体缓存,并按名称失效搜索缓存;实现中使用 GetOrSetCacheAsync<T> 统一封装带 Tag 的 FusionCache 读写。

协作流程图

接口层是 DAG 的“枢纽”,其典型数据流如下:

flowchart LR
    A[网站浏览 / 搜索] --> W[CodeController Web]
    B[API/管理端] --> API[CodeController WebApi]
    C[MCP 工具] --> M[CodeExplorerMcpTools]
    D[AI 分析消息] --> H[AnalyzeCodeHandler]

    W --> I{ICodeFileSystemEntryService}
    API --> I
    M --> I
    H --> I

    I --> S1[FindByPath / GetChildren<br/>Search / GetPagedList...]
    I --> S2[SaveDescription / SaveAiAnalyzeResult<br/>ShouldAnalyze...]

    S1 --> FC[(FusionCache 7天)]
    S2 --> INV[主动失效相关缓存]
    S1 --> DB[(MongoDB: CodeFileSystemEntry)]
    S2 --> DB
    FC -.Miss 回源.-> DB

小结

该文件本身只是一个不含实现逻辑的接口声明,其价值在于:把“代码库文件系统”相关的全部操作稳定地定义为统一契约,使 Web 展示层、WebApi 管理端、MCP 工具与后台 AI 分析任务都能面向同一抽象编程,并便于在测试中注入 Fake 实现。实现细节(缓存、MongoDB 查询、README 解析等)全部收敛在 CodeFileSystemEntryService.cs 中,接口注释中对参数语义(如 null/空表示根目录)的约定是各调用方保持一致性的重要依据。

评论加载中...