Misaka Network Blog 项目重构记录
这次重构涉及几个地方:45 篇文章从平铺目录迁移到年月层级结构,新增字数统计功能,Admin 后台适配新的文件布局。
目录结构:从平铺到年月层级
随着文章数涨到 45+ 篇,所有 Markdown 文件平铺在 src/content/blog/ 下的方式开始显得凌乱。决定改成按年月分层的结构:
旧: src/content/blog/25-11-24-16-00.md
src/content/blog/25-11-24-18-30.md
... (45 篇混在一起)
新: src/content/blog/2025/11/25-11-24-16-00.md
src/content/blog/2025/11/25-11-24-18-30.md
src/content/blog/2026/01/26-01-07-10-37.md
迁移通过一个脚本完成——从文件名提取年月信息(YY-MM-DD-HH-MM 格式的前四位),自动创建目标目录并移动文件。Astro 的 content.config.ts 本身用的是 glob({ pattern: '**/*.{md,mdx}' }),天然支持递归匹配,不需要改配置。但有几个连锁影响需要处理:
文章 ID 变了。 原来文件名就是 ID(26-01-07-10-37),现在 ID 是带路径的(2026/01/26-01-07-10-37)。sortPosts.ts 里的 getTimestampFromFilename() 需要先用 id.split('/').pop() 提取纯文件名再做正则匹配,否则会报大量「文件名格式不匹配」警告。
图片路径深了两层。 3 篇含封面图的文章,heroImage 的相对路径需要从 ../../assets/ 改成 ../../../../assets/。忘记改的后果是构建直接报 Could not find requested image。
new-post.js 需要更新。 创建新文章时自动按当前年份和月份生成目标目录,不存在则递归创建。
字数统计
在文章卡片上显示字数,实现一个支持中英文混合计数的工具函数:
export function countWords(markdown: string): number {
let content = markdown;
// 移除代码块、内联代码、链接
content = content.replace(/```[\s\S]*?```/g, '');
content = content.replace(/`[^`]+`/g, '');
content = content.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1');
// 中文字符逐个计数 + 英文单词数
const chineseCount = (content.match(/[\u4e00-\u9fa5]/g) || []).length;
const englishWords = content.match(/[a-zA-Z]+/g) || [];
return chineseCount + englishWords.length;
}
格式化用 1.2k 字 / 1.5w 字 这种缩写,通过 formatWordCount() 处理。Card 组件新增一个可选的 wordCount prop,首页、博客列表页、标签页统一接入。
Admin 后台适配
Admin 后台原本只扫描 src/content/blog/ 的单层目录,引入年月层级后需要递归遍历。getAllBlogFiles() 从单层 readdirSync 改成递归版本——遇到目录就进去,遇到 .md/.mdx 文件就收集相对路径。
API 的响应格式也做了调整,每条记录返回三个 ID 字段:
id:不含扩展名的相对路径(2026/01/26-01-07-10-37),前端用filename:纯文件名(26-01-07-10-37.md),时间戳提取和排序用relativePath:含扩展名的相对路径,文件定位用
Express 路由方面,因为文章 ID 现在包含斜杠,GET /api/posts/:id 需要改成 :id(*) 通配符语法才能正确匹配路径参数。新建文章时也自动按文件名中的年月信息创建对应目录。
顺手给后台加了一套彩色日志系统:请求耗时、文章加载、错误堆栈都有颜色标记,排查问题更直观。
验证
✅ 45 篇文章迁移,0 丢失
✅ npm run build 通过,178 页生成
✅ Admin 后台正常读取、创建、编辑文章
几点收获
目录按年月分层是在「太浅」(查找不便)和「太深」(路径冗长)之间取折中,三层(年/月/文件)对这个博客来说够用。这次的教训是:看似只改了一个目录结构,实际上连锁触发了排序逻辑、图片路径、脚本工具、Admin 后台四条链路,重构时需要把每条依赖路径都走一遍,漏掉任何一条都有可能变成线上问题。
API 设计上,返回多种 ID 格式是一种冗余但很实用的防御策略——前端、排序、文件 I/O 各取所需,不用各自再拆字符串。迁移脚本最好带上 dry-run 能力和完整性校验,Git 本身是天然的回滚机制,但改之前确认脚本输出是对的,能省掉很多回退重来的时间。