基于.NetCore开发博客项目 StarBlog - (4) markdown博客批量导入

科技资讯 投稿 47000 0 评论

基于.NetCore开发博客项目 StarBlog - (4) markdown博客批量导入

前言

上周介绍了博客的模型设计,现在模型设计好了,要开始导入数据了。

我们要把一个文件夹内的所有markdown文件导入,目录结构作为文章的分类,文件名作为文章的标题,同时把文件的创建、更新日期作为文章的发表时间。

大概的思路就是先用.Net的标准库遍历目录,用第三方的markdown解析库处理文章内容,然后通过ORM写入数据库。

PS:明天就是五一劳动节了,祝各位无产阶级劳动者节日快乐~

相关技术

  • 文件IO相关API

  • 正则表达式

  • ORM:FreeSQL

  • markdown解析库:Markdig

开始写代码

我们首先从最关键的markdown内容解析、图片提取、标题处理说起。

(菜)造轮子,只能将就了。 https://github.com/xoofx/markdig

在项目里新建一个Class:,我们要在这个class里实现markdown文件相关的处理逻辑。

https://github.com/Deali-Axy/StarBlog/blob/master/StarBlog.Migrate/PostProcessor.cs

构造方法:

private readonly Post _post;
private readonly string _importPath;
private readonly string _assetsPath;

public PostProcessor(string importPath, string assetsPath, Post post {
    _post = post;
    _assetsPath = assetsPath;
    _importPath = importPath;
}

其中

  • :我们上一篇里设计的文章模型

  • :要导入的markdown文件夹路径

  • :资源文件存放路径,用于存放markdown里的图片,本项目设置的路径是

文章摘要提取

文章摘要提取,我做了简单的处理,把markdown内容渲染成文本,然后截取前n个字形成摘要,代码如下:

public string GetSummary(int length {
    return _post.Content == null
        ? string.Empty
        : Markdown.ToPlainText(_post.Content.Limit(length;
}

文章状态和标题处理

之前在用本地markdown文件写博客的时候,出于个人习惯,我会在文件名里加上代表状态的前缀,例如未完成的文章是这样的:

(未完成)StarBlog博客开发笔记(4:markdown博客批量导入

或者已完成但未发布,会加上

等到发布之后,就把前缀去掉,所以在导入的时候,我要用正则表达式对这个前缀进行提取,让导入数据库的博客文章标题不要再带上前缀了。

代码如下

public (string, string InflateStatusTitle( {
    const string pattern = @"^((.+)(.+$";
    var status = _post.Status ?? "已发布";
    var title = _post.Title;
	if (string.IsNullOrEmpty(title return (status, $"未命名文章{_post.CreationTime.ToLongDateString(}";
    var result = Regex.Match(title, pattern;
    if (!result.Success return (status, title;

    status = result.Groups[1].Value;
    title = result.Groups[2].Value;

    _post.Status = status;
    _post.Title = title;

    if (!new[] { "已发表", "已发布" }.Contains(_post.Status {
        _post.IsPublish = false;
    }

    return (status, title;
}

逻辑很简单,判断标题是否为空(对文件名来说这不太可能,不过为了严谨一点还是做了),然后用正则匹配,匹配到了就把状态提取出来,没匹配到就默认。

图片提取 & 替换

C#解析Markdown文档,实现替换图片链接操作

然后回到我们的博客项目,这部分的代码如下

public string MarkdownParse( {
    if (_post.Content == null {
        return string.Empty;
    }

    var document = Markdown.Parse(_post.Content;

    foreach (var node in document.AsEnumerable( {
        if (node is not ParagraphBlock { Inline: { } } paragraphBlock continue;
        foreach (var inline in paragraphBlock.Inline {
            if (inline is not LinkInline { IsImage: true } linkInline continue;

            if (linkInline.Url == null continue;
            if (linkInline.Url.StartsWith("http" continue;

            // 路径处理
            var imgPath = Path.Combine(_importPath, _post.Path, linkInline.Url;
            var imgFilename = Path.GetFileName(linkInline.Url;
            var destDir = Path.Combine(_assetsPath, _post.Id;
            if (!Directory.Exists(destDir Directory.CreateDirectory(destDir;
            var destPath = Path.Combine(destDir, imgFilename;
            if (File.Exists(destPath {
                // 图片重名处理
                var imgId = GuidUtils.GuidTo16String(;
                imgFilename = $"{Path.GetFileNameWithoutExtension(imgFilename}-{imgId}.{Path.GetExtension(imgFilename}";
                destPath = Path.Combine(destDir, imgFilename;
            }

            // 替换图片链接
            linkInline.Url = imgFilename;
            // 复制图片
            File.Copy(imgPath, destPath;

            Console.WriteLine($"复制 {imgPath} 到 {destPath}";
        }
    }


    using var writer = new StringWriter(;
    var render = new NormalizeRenderer(writer;
    render.Render(document;
    return writer.ToString(;
}

实现的步骤大概是这样:

  • 用Markdig库的markdown解析功能

  • 把所有图片链接提取出来

  • 然后根据我们前面在构造方法中传入的导入目录,去拼接图片的完整路径

  • 接着把图片复制到里面

  • 最后把markdown中的图片地址替换为重新生成的图片文件名

小结

但是仍存在一个问题!图片文件名带空格时无法识别!

这个问题算是Markdig库的一个缺陷?吧,我尝试读了一下Markdig的代码想看看能不能fix一下,很遗憾我没读懂,所以暂时没有很好的办法,只能向官方提个issues了,这个库的更新很勤快,有希望让官方来修复这个问题。

遍历目录

前面说了关键的部分,现在来说一下比较简单的遍历目录文件,对文件IO用得很熟练的同学请跳过这部分~

https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/file-system/how-to-iterate-through-a-directory-treehttps://github.com/Deali-Axy/StarBlog/blob/master/StarBlog.Migrate/Program.cs

void WalkDirectoryTree(DirectoryInfo root {
    Console.WriteLine($"正在扫描文件夹:{root.FullName}";

    FileInfo[]? files = null;
    DirectoryInfo[]? subDirs = null;

    try {
        files = root.GetFiles("*.md";
    }
    catch (UnauthorizedAccessException e {
        Console.WriteLine(e.Message;
    }
    catch (DirectoryNotFoundException e {
        Console.WriteLine(e.Message;
    }

    if (files != null {
        foreach (var fi in files {
            Console.WriteLine(fi.FullName;
            // 处理文章的代码,省略
        }
    }

    subDirs = root.GetDirectories(;

    foreach (var dirInfo in subDirs {
        if (exclusionDirs.Contains(dirInfo.Name {
            continue;
        }

        if (dirInfo.Name.EndsWith(".assets" {
            continue;
        }

        WalkDirectoryTree(dirInfo;
    }
}

用的这个方法叫做“前序遍历”,即先处理目录下的文件,然后再处理目录下的子目录。

递归的方法写起来比较简单,但是有一个缺陷是如果目录结构嵌套太多的话,可能会堆栈溢出,可以考虑换用基于模式的遍历,不过作为博客的目录层级结构应该不会太多,所以我只用简单的~

写入数据库

https://github.com/Deali-Axy/StarBlog/blob/master/StarBlog.Migrate/Program.cs

结束

OK,博客批量导入就介绍了这么多,几个麻烦的地方处理好之后也没啥难度了,有了文章数据之后,才能方便接下来开始开发博客网站~

大概就这些了,下篇文章见~

同时所有项目代码已经上传GitHub,欢迎各位大佬Star/Fork!

  • 博客后端+前台项目地址:https://github.com/Deali-Axy/StarBlog

  • 管理后台前端项目地址:https://github.com/Deali-Axy/StarBlog-Admin

编程笔记 » 基于.NetCore开发博客项目 StarBlog - (4) markdown博客批量导入

赞同 (26) or 分享 (0)
游客 发表我的评论   换个身份
取消评论

表情
(0)个小伙伴在吐槽