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)
- 构建侧边栏树形导航
关键逻辑:
- 解析路径参数,拆分成路径数组
- 调用服务构建代码树结构
- 智能分析触发: 如果是代码文件且需要分析,则发布分析消息到消息队列
- 异步消息发布包含:文件路径、文件名、文件哈希(用于去重)、代码内容
- 若有 AI 分析结果,将其设置为页面描述(SEO 优化)
- 构建侧边栏树形结构并返回视图
返回:
- 成功:返回
CodeIndexViewModel视图 - 失败:返回 404 NotFound
2. TreeChildren - 动态加载子节点
路由: GET /get/code/children
功能:
- Ajax 请求动态加载目录的子节点
- 支持懒加载树形结构
关键逻辑:
- 解析路径参数
- 路径验证: 如果路径对应真实存在的条目,重定向到正常 Index 页面(防止路由冲突)
- 查询子节点,分为目录和文件两组
- 分别排序后返回部分视图
返回: _CodeTreeRowsPartial 部分视图,包含 (目录列表, 文件列表)
3. Search - 代码搜索
路由: GET /search/code
功能:
- 根据关键字搜索代码文件
关键逻辑:
- 调用服务执行搜索
- 将搜索结果转换为视图模型
返回: _CodeSearchResultsPartial 部分视图
辅助私有方法
BuildSidebarTreeAsync - 构建侧边栏树
功能: 构建初始侧边栏树形结构,根据当前路径逐级展开
算法:
1. 加载根级目录项
2. 遍历当前路径的各个层级
3. 找到匹配的文件夹节点
4. 递归加载该节点的子级
5. 标记为已展开
BuildLevelItemsAsync - 构建单层级节点
功能: 加载指定路径下的直接子项
排序规则:
- 目录优先(IsDirectory)
- 相同类型按名称排序
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 辅助代码分析平台
- 企业内部源码管理门户
AI 正在分析代码…
评论加载中...