Xiaokeer CMS 是一个基于 .NET 8 的 Windows 桌面内容管理系统,面向个人用户,用于统一管理本地散落在各处的文件(视频、音频、文档、图片等)。
本地电脑中存在大量分散在不同文件夹中的文件,缺乏统一的管理方式,查找和利用效率低。Xiaokeer CMS 通过将文件托管到统一存储目录并建立元数据索引,提供高效的检索、查看和对外访问能力。
| 项目 | 技术 |
|---|---|
| 语言 | C# |
| 框架 | .NET 8 (LTS) |
| GUI | WPF (MVVM) |
| 数据库 | SQLite (Entity Framework Core) |
| HTTP 服务 | ASP.NET Core Minimal APIs (内嵌 Kestrel) |
| MVVM 工具 | CommunityToolkit.Mvvm |
- 从 Windows 文件资源管理器拖拽文件或文件夹到应用窗口
- 自动识别文件类型(视频、音频、图片、文档、其他)
- 将文件剪切(移动)到统一存储目录,原路径不再保留该文件
- 为每个文件生成 UUID,记录元数据(原始文件名、类型、大小、导入时间等)存入 SQLite
- Markdown 文件特殊处理:导入
.md文件时自动解析其中的图片引用(base64 内嵌图片、HTTP/HTTPS 网络链接、本地相对路径),将图片提取并存储到{contentId}_img/文件夹,同时将 Markdown 中的图片引用重写为相对路径,确保图片引用不会因文件移动而失效 - 文件夹托管二次确认:拖拽文件夹时,先递归统计可托管文件数量,弹窗提示用户确认后才开始托管,取消则放弃操作
- 托管进度窗口:批量导入时显示实时进度窗口,包含当前正在托管的文件名、成功/失败数量、预期总数、已耗时、进度条。导入完成前窗口不可关闭,完成后点击确认按钮关闭
- 列表展示所有已托管的内容,显示文件名、文档栏、类型、大小、导入时间、扩展名、状态、MDX状态
- 支持按文件名搜索、按文件类型筛选、按文件扩展名筛选、按文档栏筛选、按状态筛选、按MDX状态筛选、按日期范围过滤
- 文件扩展名筛选:下拉列表提供所有已定义的扩展名选项,热门扩展名(.md、.txt、.pdf 等)排在前面
- 文档栏筛选:下拉列表动态从数据库获取去重后的文档栏列表("ALL" + 去重列表),默认选择 "ALL" 表示不筛选,选择其他项则筛选对应的文档栏内容
- 状态筛选:下拉列表提供选项("ALL"、"完整"、"不完整"),默认选择 "ALL" 表示不筛选,选择"完整"只显示文件存在的项目,选择"不完整"只显示文件丢失的项目
- MDX状态筛选:下拉列表提供选项("ALL"、"不支持MDX"、"MDX不存在"、"MDX不同步"、"MDX同步"),默认选择 "ALL" 表示不筛选,选择其他项则筛选对应的MDX转储状态
- 内容状态检验:每次查询时实时检验托管文件是否存在,显示绿色"完整"或红色"不完整"状态标签
- MDX状态判断:对于 Markdown 文件,根据设置的"md转储mdx文档路径配置"判断 MDX 文件是否存在及同步状态
- 不支持MDX(天蓝色):非 Markdown 文件
- MDX不存在(橙色):MDX 文件不存在
- MDX不同步(红色):MDX 文件存在但与源文件不同步(通过文件大小判断)
- MDX同步(绿色):MDX 文件存在且与源文件同步(通过文件大小判断)
- 日期格式校验:输入非法日期格式或起始日期大于结束日期时弹窗提示
- 点击列头可排序
- 双击打开文件:双击内容列表中的某一行可直接打开对应文件(需在 Settings tab 中启用"允许双击打开文件"选项)
- 批量操作支持:所有右键菜单操作均支持多选批量执行。选中多个内容项后右键点击,操作将应用于所有选中项。批量操作遇到失败时立即停止,显示已成功处理的数量
- 右键菜单:
- Open File — 使用系统默认程序打开文件(文件不存在时弹窗提示)
- Open MDX File — 使用系统默认程序打开转储的 MDX 文件(仅限 .md 文件,非MD文件或MDX文件不存在时弹窗提示)
- Open File Location — 在 Windows 资源管理器中定位文件(文件不存在时弹窗提示)
- Rename — 重命名内容项,弹出对话框输入新名称(支持 Enter 键确认和 X 键取消)
- ReDocsSection — 修改文档栏,弹出对话框输入新的文档栏名称(只允许中文、英文、数字)
- SyncMDX — 将 MD 文件转储为 MDX 格式到配置的目标路径。转储时自动生成 YAML Frontmatter(title = 文件名,section = 文档栏),并验证内容是否符合 xiaokeer-discover 项目的 Velite 配置要求。图片处理: 如果 MD 文件有关联的图片文件夹(
{uuid}_img),转储时会:(1) 将图片文件夹复制到 Settings 中配置的"MDX 图片路径";(2) 转换图片引用路径,将 MD 中的相对路径./开头替换为/(MDX 使用绝对路径) - Restore — 还原内容对象到指定路径。将托管文件从 CMS 存储目录移动到用户指定的还原路径(默认为桌面),同时删除数据库记录。对于 MD 文件,会同时处理关联的图片文件夹(
{uuid}_img),将其重命名为{文件名}_img并与 MD 文件一起放入同名文件夹中 - Delete — 删除内容记录及存储的文件(文件不存在时拒绝删除并提示恢复,文件被占用时提示关闭占用程序)
- 创建 MD 文件 — 在 Tools tab 中点击"创建 MD"按钮,弹出对话框输入文件名和 Markdown 内容,确认后在
%USERPROFILE%\.xiaokeer_cms\collocation_create_md\目录下创建文件,创建成功后可打开文件所在目录 - 根据ImgDir创建MD — 点击"根据ImgDir"按钮,弹出文件夹选择对话框,选择包含图片的文件夹后,系统会在
%USERPROFILE%\.xiaokeer_cms\tool_CreateMdByImgsDir\目录下创建新文件夹(格式:原文件夹名称_uuid),该文件夹内包含:- MD 文件(名称为原文件夹名称
.md):每行一个图片名称 + 一个图片引用,格式为图片名称\n - 图片文件夹(使用随机UUID命名):复制原文件夹中的所有图片,文件名自动清理(只保留中文字符和英文字母,去除数字、符号、空格等)
- 原文件夹重命名:处理成功后,原文件夹会自动重命名为
原文件夹名称_toMDok,标识已处理 - 路径记忆:第二次使用时会自动定位到上次选择的父目录,方便继续处理同级其他文件夹
- 支持图片格式:
.jpg、.jpeg、.png、.gif、.bmp、.webp
- MD 文件(名称为原文件夹名称
- 执行Python脚本1 — 点击"执行脚本1"按钮,弹出窗口执行
C:\p1.py脚本,显示脚本内容和执行结果 - 执行Python脚本2 — 点击"执行脚本2"按钮,弹出窗口执行
C:\p2.py脚本,显示脚本内容和执行结果 - 执行Python脚本3 — 点击"执行脚本3"按钮,弹出窗口执行
C:\p3.py脚本,显示脚本内容和执行结果 - 按扩展名分类文件 — 点击"按扩展名分类文件"按钮,弹出文件夹选择对话框,选择文件夹后程序会自动扫描该文件夹的一级文件(不包括子文件夹),根据文件扩展名创建对应的子文件夹并将文件移动到对应位置:
- 文件夹以扩展名命名(如
pdf、docx、png) - 无扩展名的文件会被忽略
- 如果目标位置已存在同名文件,会自动重命名为
文件名_1格式 - 处理完成后显示详细统计信息(总数、忽略数、成功数、失败数、按扩展名分布)
- 文件夹以扩展名命名(如
- 允许双击打开文件 — CheckBox 选项,默认关闭。开启后双击内容列表行可直接打开文件,关闭后双击无反应。即时生效,无需 Apply 按钮
- 删除文件时,无需二次确认 — CheckBox 选项,默认关闭。开启后删除内容时,不会弹窗二次确认,直接删除了, 关闭后删除内容时会弹窗二次确认, 询问用户是否确认删除, 即时生效, 无需 Apply 按钮
- 仅托管md文件 — CheckBox 选项,默认关闭。勾选后,拖拽文件夹时仅处理.md文件,其他类型文件将被忽略。如果文件夹中没有任何.md文件,会显示错误提示并中断导入。适用于只托管 Markdown 文件而忽略其配套图片文件夹的场景
- 窗口始终置顶 — CheckBox 选项,默认关闭。勾选后,窗口将始终显示在最前面,不被其他窗口遮挡。适用于需要窗口保持可见性的场景。即时生效,无需 Apply 按钮(非持久化设置,重启应用后恢复为默认值)
- md轉儲mdx文檔路徑配置 — 文本框 + 浏览/打开按钮,默认值:
C:\project\xiaokeer-discover\content\docs - md圖片轉儲mdx文檔圖片路徑配置 — 文本框 + 浏览/打开按钮,默认值:
C:\project\xiaokeer-discover\public\img
- python解釋器路徑配置 — 文本框 + 浏览/打开按钮,默认值:
C:\Users\Administrator\AppData\Local\Programs\Python\Python314\python.exe
应用启动时内嵌 HTTP 服务器,监听 http://localhost:15080,提供以下接口:
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/contents |
获取内容列表,支持查询参数 search、category、from、to、extension |
| GET | /api/contents/{id} |
根据 ID 获取内容元数据 |
| GET | /api/contents/{id}/download |
下载文件(Markdown 文件含图片时返回 ZIP 包) |
| GET | /api/contents/{id}/html |
将 Markdown 文件转换为 HTML 页面返回(含目录导航、代码高亮、图片以 base64 内嵌展示) |
| GET | /api/contents/{id}/images/{filename} |
获取 Markdown 文件关联的单张图片 |
XiaokeerCms/
├── Models/ # 数据模型
│ ├── ContentItem.cs # 内容实体(UUID、文件名、类型、路径、大小、时间)
│ ├── FileCategory.cs # 文件分类枚举(Video, Audio, Image, Document, Other)
│ └── MdxExportStatus.cs # MDX 转储状态枚举(NotSupported, MdxNotExists, MdxOutOfSync, MdxSynced)
├── Data/
│ └── AppDbContext.cs # EF Core 数据库上下文
├── Services/ # 业务逻辑层
│ ├── FileCategoryDetector.cs # 根据扩展名识别文件分类
│ ├── IFileStorageService.cs
│ ├── FileStorageService.cs # 文件移动、路径解析、删除
│ ├── IContentService.cs
│ ├── ContentService.cs # 内容导入、查询、删除、重命名
│ ├── MarkdownImageProcessor.cs # Markdown 图片解析、提取、路径重写
│ └── MarkdownHtmlConverter.cs # Markdown 转 HTML 转换(Markdig + TOC + 代码高亮)
├── ViewModels/ # MVVM ViewModel 层
│ ├── MainViewModel.cs # 主界面逻辑(列表、筛选、命令)
│ └── ContentItemViewModel.cs # 单条内容的显示包装
├── Views/ # WPF 视图层
│ ├── MainWindow.xaml # 主窗口布局
│ ├── MainWindow.xaml.cs # 拖拽事件、列头排序
│ ├── CreateMarkdownDialog.xaml # 创建 Markdown 文件对话框
│ ├── CreateMarkdownDialog.xaml.cs # 对话框交互逻辑
│ ├── ImportProgressDialog.xaml # 托管进度对话框
│ ├── ImportProgressDialog.xaml.cs # 进度窗口逻辑(计时、进度更新、关闭拦截)
│ ├── RenameContentDialog.xaml # 重命名内容对话框
│ ├── RenameContentDialog.xaml.cs # 重命名对话框逻辑
│ ├── ReDocsSectionDialog.xaml # 修改文档栏对话框
│ ├── ReDocsSectionDialog.xaml.cs # 修改文档栏对话框逻辑
│ ├── PythonScriptDialog.xaml # Python 脚本执行对话框
│ └── PythonScriptDialog.xaml.cs # Python 脚本执行对话框逻辑
├── Api/
│ └── ContentEndpoints.cs # HTTP API 路由定义
├── DatabaseScripts/
│ └── InitializeDocsSection.sql # DocsSection 字段初始化脚本
├── Hosting/
│ └── AppHostBuilder.cs # Generic Host 构建(DI、Kestrel、EF Core)
├── Helpers/
│ ├── PathHelper.cs # 存储路径解析工具
│ └── ProcessHelper.cs # 调用系统程序打开文件/文件夹
├── App.xaml
└── App.xaml.cs # 应用启动/退出生命周期管理
scripts/ # Node.js 脚本目录
├── package.json # Node.js 依赖配置(@mdx-js/mdx 等)
└── validate-mdx.mjs # MDX 验证脚本,使用与 xiaokeer-discover 相同的编译引擎
所有数据存储在用户目录下的 .xiaokeer_cms 文件夹中:
%USERPROFILE%\.xiaokeer_cms\
├── xiaokeer_cms.db # SQLite 数据库文件
├── collocation_create_md/ # "创建 MD 文件"工具的输出目录
├── tool_CreateMdByImgsDir/ # "根据ImgDir创建MD"工具的输出目录
└── storage/ # 托管文件存储根目录
├── video/ # 视频文件 ({uuid}.mp4, {uuid}.mkv, ...)
├── audio/ # 音频文件 ({uuid}.mp3, {uuid}.flac, ...)
├── image/ # 图片文件 ({uuid}.jpg, {uuid}.png, ...)
├── document/ # 文档文件 ({uuid}.pdf, {uuid}.docx, ...)
│ # Markdown 文件的关联图片存储在 {uuid}_img/ 子文件夹中
└── other/ # 其他文件 ({uuid}.zip, ...)
- 文件以 UUID 重命名存储,避免命名冲突
- 按文件分类放入对应子目录
- 原始文件名保留在数据库中,用于显示和下载
ContentItems 表:
| 字段 | 类型 | 说明 |
|---|---|---|
| Id | GUID | 主键 |
| OriginalFileName | TEXT | 原始文件名(不含后缀) |
| FileExtension | TEXT | 文件扩展名(如 .pdf) |
| FileCategory | INTEGER | 文件分类(0=Video, 1=Audio, 2=Image, 3=Document, 4=Other) |
| StorageRelativePath | TEXT | 相对存储路径(如 document/{uuid}.pdf) |
| FileSize | INTEGER | 文件大小(字节) |
| CreatedAt | TEXT | 导入时间(UTC) |
| OriginalPath | TEXT | 文件导入前的原始完整路径 |
| DocsSection | TEXT | 文档栏(只允许中文、英文、数字,默认为"其他") |
MDX 转储状态(临时字段):
- 不支持MDX(天蓝色):非 Markdown 文件
- MDX不存在(橙色):MDX 文件不存在
- MDX不同步(红色):MDX 文件存在但与源文件不同步(当前通过文件大小判断)
- MDX同步(绿色):MDX 文件存在且与源文件同步(当前通过文件大小判断)
# 还原依赖
dotnet restore
# 安装 Node.js 依赖(MDX 验证需要,首次运行前执行)
cd scripts && npm install && cd ..
# 编译
dotnet build
# 运行
dotnet run --project XiaokeerCms应用启动后:
- GUI 窗口自动打开
- HTTP 服务监听
http://localhost:15080 - 数据库和存储目录自动创建在
%USERPROFILE%\.xiaokeer_cms\
dotnet publish -c Release -p:PublishProfile=FolderPublish应用会被发布到 Publish/XiaokeerCms/ 目录(约 203MB),包含:
- 自包含的 .NET 8 运行时(无需预先安装 .NET)
- XiaokeerCms.exe 可执行文件
- 完整的目录结构,包括 MDX 验证所需的 scripts 目录
分发方式:
- 将
Publish/XiaokeerCms/文件夹压缩为 zip 文件 - 发送给用户,用户解压后直接运行
XiaokeerCms.exe即可使用
减小体积: 如果需要减小发布体积(约 50MB,但启动速度会变慢),可以禁用 ReadyToRun:
dotnet publish -c Release -p:PublishProfile=FolderPublish -p:PublishReadyToRun=false| 文档 | 职责 | 维护原则 |
|---|---|---|
| README.md | 项目总览,面向所有读者 | 反映项目最新最全状态,任何功能变更都需同步更新 |
| AGENTS.md | 通用智能体开发指南 | 只包含构建命令、代码风格、架构概述,不包含具体功能描述 |
| CLAUDE.md | Claude智能体专用指南 | 保持现有风格,只包含构建命令、代码风格、架构概述、数据流描述 |
- 功能新增:在对应章节添加功能描述
- 功能修改:直接修改原有描述,不追加补充说明
- 功能删除:删除对应描述,不留历史记录
- 数据库变更:同步更新数据库表结构说明
- API变更:同步更新API接口表格
- ❌ 在 README.md 中追加"xxxx年xx月xx日已修改"类型的补充说明
- ❌ 在 AGENTS.md 或 CLAUDE.md 中添加具体功能描述
- ❌ 在 AGENTS.md 或 CLAUDE.md 中添加代码实现细节
- ❌ 让 AGENTS.md 和 CLAUDE.md 内容完全相同(需保留各自风格)