字
字节笔记本
2026年2月22日
mdblog:Go 语言开发的 Markdown 博客系统核心代码解析
本文介绍 mdblog 项目中的文章解析核心代码,展示如何使用 Go 语言读取和解析 Markdown 格式的博客文章。
项目背景
mdblog 是 Bro Qiang 开发的一个基于 Go 语言的博客系统,采用动静结合的方式:博客源文件(Markdown)放在 GitHub 上,通过 Git 同步更新,博客系统自动解析展示。项目地址:github.com/broqiang/mdblog
核心代码解析
Article 结构体定义
go
// Article 文章内容
type Article struct {
// 文章的标题
Title string
// 作者姓名
Author string
// 创建时间
CreatedAt time.Time `toml:"created_at"`
// 最后更新时间
UpdatedAt time.Time `toml:"updated_at"`
// 标签
Tags []string
// 所属分类的名称
Category string
// 头部图片 URL 地址
HeadImg string `toml:"head_img"`
// 作者的个人主页
HomePage string `toml:"home_page"`
// 简短的描述
Description string
// 文章主题内容, markdown
Body string
// 文章在服务器上的文件路由
Path string
}获取文章列表
go
// 获取指定分类下的文章
func getAritclesSpecifiedCategory(category *Category) Articles {
// 获取分类下文件的数量
path := filepath.Join(getRootPath(), category.Path)
// 确认目录是否为空,并且目录下是否有文件
filesInfo, err := ioutil.ReadDir(path)
if os.IsNotExist(err) {
return nil
}
helper.PanicErr(err)
number := 0
articles := make([]Article, 0)
for _, info := range filesInfo {
// 如果目录下的是文件,就不处理
if info.IsDir() {
continue
}
// 获取文件名
fileName := info.Name()
// 获取文件名的后缀
ext := filepath.Ext(fileName)
// 如果后缀名不是 .md 就不处理
if ext != ".md" {
continue
}
// 获取文章 struct
article := getArticleContent(filepath.Join(path, fileName))
if reflect.DeepEqual(article, Article{}) {
continue
}
// 将分类添加到文章中
article.Category = category.Path
article.Path = strings.TrimSuffix(info.Name(), ext)
articles = append(articles, article)
number++
}
category.Number = number
return articles
}解析 Markdown 文章内容
go
// 获取 markdown 中的文章信息
func getArticleContent(path string) Article {
article := Article{}
// 读取文件
bytes, err := ioutil.ReadFile(path)
if err != nil {
mylog.LogErr.Printf("read %q failure, %v", path, err)
return article
}
// 字符串格式的文档
str := string(bytes)
// 将字符串按照 +++ 截取
arr := strings.SplitN(str, "+++", 3)
// 正常截取完是 3 部分,如果不是,就直接返回 nil
if len(arr) != 3 {
mylog.LogErr.Printf("file %q is incomplete, intercepted failure", path)
return article
}
head := strings.TrimSpace(arr[1])
body := strings.TrimSpace(arr[2])
if head == "" || body == "" {
mylog.LogErr.Printf("file %q head or body is empty", path)
return article
}
// 解析头部的 toml
if _, err := toml.Decode(head, &article); err != nil {
mylog.LogErr.Printf("file %q parse toml head failure, %v", path, err)
return article
}
article.Body = body
return article
}文章排序实现
mdblog 实现了 Go 的 sort.Interface 接口,支持对文章进行多维度排序:
go
// Len
func (a Articles) Len() int {
return len(a)
}
// Swap 实现的 sort 接口
func (a Articles) Swap(i, j int) {
a[i], a[j] = a[j], a[i]
}
// Less 进行排序比较
// 第一排序是 UpdatedAt,第二排序是 CreatedAt,第三排序是 Title
func (a Articles) Less(i, j int) bool {
if a[i].UpdatedAt.After(a[j].UpdatedAt) {
return true
}
if a[i].CreatedAt.After(a[j].CreatedAt) {
return true
}
return false
}Markdown 文件格式
mdblog 使用 TOML 格式的文章头部(Front Matter),用 +++ 分隔:
markdown
+++
title = "文章标题"
author = "作者名"
created_at = 2019-04-25T10:00:00Z
updated_at = 2019-04-25T10:00:00Z
tags = ["go", "blog"]
head_img = "https://example.com/image.jpg"
description = "文章简介"
+++
这里是 Markdown 格式的正文内容...技术亮点
- TOML 解析:使用
github.com/BurntSushi/toml库解析文章元数据 - 错误处理:完善的错误日志记录,确保系统稳定性
- 排序功能:实现 sort 接口,支持多维度文章排序
- 文件过滤:自动过滤非 Markdown 文件和空目录
项目信息
- GitHub 仓库:github.com/broqiang/mdblog
- Stars:363
- Forks:68
- 开发语言:Go
- 许可证:MIT
分享: