using Dpz.Core.EnumLibrary;
using Dpz.Core.MessageQueue.Abstractions;
using Dpz.Core.Public.ViewModel;
using Dpz.Core.Public.ViewModel.Messages;
using Dpz.Core.Public.ViewModel.Response;
using Dpz.Core.Web.Models;

namespace Dpz.Core.Web.Controllers;

public class CodeController(
    ICodeFileSystemEntryService codeFileSystemEntryService,
    IMessagePublisher<AnalyzeCodeMessage> analyzeCodePublisher
) : Controller
{
    [Route("code/{**path}")]
    [HttpGet]
    public async Task<IActionResult> Index(
        string path = "",
        CancellationToken cancellationToken = default
    )
    {
        ViewBag.Menu = MenuItem.Code;
        this.SetTitle(string.IsNullOrEmpty(path) ? "网站源码" : path + " 网站源码");

        var pathArray = path.Split("/").Where(x => !string.IsNullOrWhiteSpace(x)).ToArray();
        var model = await codeFileSystemEntryService.BuildCodeNoteTreeAsync(
            pathArray,
            cancellationToken: cancellationToken
        );

        if (
            model is { CodeContainer.CodeContent: not null, FileName: not null }
            && await codeFileSystemEntryService.ShouldAnalyzeAsync(
                model.CurrentPaths.ToArray(),
                model.FileName,
                model.CodeContainer,
                cancellationToken
            )
        )
        {
            // 重新查询获取文件Hash用于消息去重
            var entry = await codeFileSystemEntryService.FindByPathAsync(
                model.CurrentPaths.ToArray(),
                cancellationToken
            );
            if (entry?.Hash != null)
            {
                var parentPath = model.ParentPaths.ToArray();
                await analyzeCodePublisher.PublishAsync(
                    new AnalyzeCodeMessage
                    {
                        Path = parentPath,
                        FileName = model.FileName,
                        FileHash = entry.Hash,
                        CodeContent = model.CodeContainer.CodeContent,
                    },
                    cancellationToken: cancellationToken
                );
            }
        }

        if (model is not { Type: FileSystemType.FileSystem })
        {
            return NotFound();
        }

        if (!string.IsNullOrWhiteSpace(model.CodeContainer?.AiAnalyzeResult))
        {
            var pageMetaPage = new VmPageMetadata
            {
                Description = model.CodeContainer?.AiAnalyzeResult,
                Keywords = [.. pathArray],
                Relations = ["Code", "Index"],
            };
            ViewData["PageMetadata"] = pageMetaPage;
        }

        var sidebarTree = await BuildSidebarTreeAsync(pathArray, HttpContext.RequestAborted);
        return View(new CodeIndexViewModel { NoteTree = model, SidebarTree = sidebarTree });
    }

    [Route("get/code/children")]
    [HttpGet]
    public async Task<IActionResult> TreeChildren(
        string path = "",
        CancellationToken cancellationToken = default
    )
    {
        var pathArray = string.IsNullOrWhiteSpace(path)
            ? []
            : path.Split("/").Where(x => !string.IsNullOrWhiteSpace(x)).ToArray();

        if (pathArray.Length > 0)
        {
            // 若该路径真实存在,则交回正常页面处理,避免路由抢占真实目录
            var entry = await codeFileSystemEntryService.FindByPathAsync(
                pathArray,
                cancellationToken
            );
            if (entry != null)
            {
                return RedirectToAction(nameof(Index), new { path });
            }
        }

        var children = await codeFileSystemEntryService.GetChildrenAsync(
            pathArray,
            cancellationToken
        );
        var directories = children
            .Where(x => x.IsDirectory)
            .Select(ToChildrenTree)
            .OrderBy(x => x.Name)
            .ToList();
        var files = children
            .Where(x => !x.IsDirectory)
            .Select(ToChildrenTree)
            .OrderBy(x => x.Name)
            .ToList();
        return PartialView("_CodeTreeRowsPartial", (directories, files));
    }

    [Route("search/code")]
    [HttpGet]
    public async Task<IActionResult> Search(
        string keyword = "",
        CancellationToken cancellationToken = default
    )
    {
        var matchList = await codeFileSystemEntryService.SearchAsync(keyword, cancellationToken);
        var model = CodeSearchResultsViewModel.FromSearch(keyword, matchList);
        return PartialView("_CodeSearchResultsPartial", model);
    }

    /// <summary>
    /// 构建侧边栏初始树:根级 + 当前路径各级,逐级展开
    /// </summary>
    private async Task<List<CodeTreeNodeViewModel>> BuildSidebarTreeAsync(
        string[] pathSegments,
        CancellationToken cancellationToken
    )
    {
        var rootItems = await BuildLevelItemsAsync([], cancellationToken);
        var levelItems = rootItems;

        for (var i = 0; i < pathSegments.Length; i++)
        {
            var segment = pathSegments[i];
            var node = levelItems.FirstOrDefault(x =>
                x.IsFolder && string.Equals(x.Name, segment, StringComparison.OrdinalIgnoreCase)
            );
            if (node == null)
            {
                break;
            }

            node.Children = await BuildLevelItemsAsync(pathSegments[..(i + 1)], cancellationToken);
            levelItems = node.Children;
        }

        return rootItems;
    }

    private async Task<List<CodeTreeNodeViewModel>> BuildLevelItemsAsync(
        string[] pathSegments,
        CancellationToken cancellationToken
    )
    {
        var children = await codeFileSystemEntryService.GetChildrenAsync(
            pathSegments,
            cancellationToken
        );
        return children
            .OrderBy(x => x.IsDirectory ? 0 : 1)
            .ThenBy(x => x.Name)
            .Select(x => new CodeTreeNodeViewModel
            {
                Name = x.Name,
                Path = x.PathSegments.ToList(),
                IsFolder = x.IsDirectory,
            })
            .ToList();
    }

    private static ChildrenTree ToChildrenTree(CodeFileSystemEntryResponse entry)
    {
        return new ChildrenTree
        {
            Name = entry.Name,
            CurrentPath = entry.PathSegments.ToList(),
            LastUpdateTime = entry.LastWriteTime,
            Note = entry.Description ?? "",
        };
    }
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

代码说明文档

概述

这是一个 ASP.NET Core MVC 控制器类 CodeController,主要负责处理代码文件系统的浏览、搜索和展示功能。它提供了类似文件浏览器的 Web 界面,支持树形目录结构展示、代码分析以及搜索功能。

类结构

构造函数依赖注入

public CodeController(
    ICodeFileSystemEntryService codeFileSystemEntryService,
    IMessagePublisher<AnalyzeCodeMessage> analyzeCodePublisher
)
  • codeFileSystemEntryService: 代码文件系统服务,处理文件/目录的查询操作
  • analyzeCodePublisher: 消息发布器,用于异步发送代码分析任务

主要功能方法

1. Index - 主页面展示

路由: GET /code/{**path}

功能:

  • 展示指定路径的代码文件/目录内容
  • 自动触发 AI 代码分析(如需要)
  • 设置页面元数据(SEO)
  • 构建侧边栏树形导航

关键逻辑:

  1. 解析路径参数,拆分成路径数组
  2. 调用服务构建代码树结构
  3. 智能分析触发: 如果是代码文件且需要分析,则发布分析消息到消息队列
  4. 异步消息发布包含:文件路径、文件名、文件哈希(用于去重)、代码内容
  5. 若有 AI 分析结果,将其设置为页面描述(SEO 优化)
  6. 构建侧边栏树形结构并返回视图

返回:

  • 成功:返回 CodeIndexViewModel 视图
  • 失败:返回 404 NotFound

2. TreeChildren - 动态加载子节点

路由: GET /get/code/children

功能:

  • Ajax 请求动态加载目录的子节点
  • 支持懒加载树形结构

关键逻辑:

  1. 解析路径参数
  2. 路径验证: 如果路径对应真实存在的条目,重定向到正常 Index 页面(防止路由冲突)
  3. 查询子节点,分为目录和文件两组
  4. 分别排序后返回部分视图

返回: _CodeTreeRowsPartial 部分视图,包含 (目录列表, 文件列表)


路由: GET /search/code

功能:

  • 根据关键字搜索代码文件

关键逻辑:

  1. 调用服务执行搜索
  2. 将搜索结果转换为视图模型

返回: _CodeSearchResultsPartial 部分视图


辅助私有方法

BuildSidebarTreeAsync - 构建侧边栏树

功能: 构建初始侧边栏树形结构,根据当前路径逐级展开

算法:

1. 加载根级目录项
2. 遍历当前路径的各个层级
3. 找到匹配的文件夹节点
4. 递归加载该节点的子级
5. 标记为已展开

BuildLevelItemsAsync - 构建单层级节点

功能: 加载指定路径下的直接子项

排序规则:

  1. 目录优先(IsDirectory)
  2. 相同类型按名称排序

ToChildrenTree - 数据转换

功能: 将 CodeFileSystemEntryResponse 转换为 ChildrenTree 视图模型


技术特点

1. 异步消息队列集成

通过消息发布器异步触发代码分析,避免阻塞主请求:

await analyzeCodePublisher.PublishAsync(new AnalyzeCodeMessage {...})

2. 消息去重机制

使用文件哈希值作为消息的唯一标识,防止重复分析同一文件

3. SEO 优化

将 AI 分析结果设置为页面元数据:

ViewData["PageMetadata"] = new VmPageMetadata {
    Description = model.CodeContainer?.AiAnalyzeResult,
    Keywords = pathArray
}

4. 智能路由处理

TreeChildren 中检测真实路径,防止 Ajax 路由抢占实际目录路由

5. CancellationToken 支持

所有异步操作支持取消令牌,提升应用响应性


数据流示意

用户请求 → Index 
    ↓
解析路径 → 构建代码树 
    ↓
需要分析? → 发布消息到队列(异步)
    ↓
设置页面元数据 
    ↓
构建侧边栏树 
    ↓
返回视图

应用场景

  • 在线代码仓库浏览器
  • 代码文档生成系统
  • AI 辅助代码分析平台
  • 企业内部源码管理门户
评论加载中...