天天看點

基于.NetCore開發部落格項目 StarBlog - (4) markdown部落格批量導入

系列文章

  • 基于.NetCore開發部落格項目 StarBlog - (1) 為什麼需要自己寫一個部落格?
  • 基于.NetCore開發部落格項目 StarBlog - (2) 環境準備和建立項目
  • 基于.NetCore開發部落格項目 StarBlog - (3) 模型設計
  • 基于.NetCore開發部落格項目 StarBlog - (4) markdown部落格批量導入
  • 基于.NetCore開發部落格項目 StarBlog - (5) 開始搭建Web項目
  • 基于.NetCore開發部落格項目 StarBlog - (6) 頁面開發之部落格文章清單
  • 基于.NetCore開發部落格項目 StarBlog - (7) 頁面開發之文章詳情頁面
  • 基于.NetCore開發部落格項目 StarBlog - (8) 分類層級結構展示
  • 基于.NetCore開發部落格項目 StarBlog - (9) 圖檔批量導入
  • 基于.NetCore開發部落格項目 StarBlog - (10) 圖檔瀑布流
  • 基于.NetCore開發部落格項目 StarBlog - (11) 實作通路統計
  • 基于.NetCore開發部落格項目 StarBlog - (12) Razor頁面動态編譯
  • ...

前言

上周介紹了部落格的模型設計,現在模型設計好了,要開始導入資料了。

我們要把一個檔案夾内的所有markdown檔案導入,目錄結構作為文章的分類,檔案名作為文章的标題,同時把檔案的建立、更新日期作為文章的發表時間。

大概的思路就是先用.Net的标準庫周遊目錄,用第三方的markdown解析庫處理文章内容,然後通過ORM寫入資料庫。

PS:明天就是五一勞動節了,祝各位無産階級勞動者節日快樂~

相關技術

  • 檔案IO相關API
  • 正規表達式
  • ORM:FreeSQL
  • markdown解析庫:Markdig

開始寫代碼

我們首先從最關鍵的markdown内容解析、圖檔提取、标題處理說起。

為了處理markdown内容,我搜了一下相關資料,發現.Net Core目前能用的隻有

Markdig

這個庫,由于還處在開發階段,沒有完整文檔,隻能邊看github首頁的一點點說明邊自己結合例子來用。沒辦法,沒别的好的選擇,又懶得(菜)造輪子,隻能将就了。

Markdig官網位址:https://github.com/xoofx/markdig

StarBlog.Migrate

項目裡建立一個Class:

PostProcessor

,我們要在這個class裡實作markdown檔案相關的處理邏輯。

PostProcessor.cs

的完整代碼在這: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;
}
           

其中

  • Post

    :我們上一篇裡設計的文章模型
  • importPath

    :要導入的markdown檔案夾路徑
  • assetsPath

    :資源檔案存放路徑,用于存放markdown裡的圖檔,本項目設定的路徑是

    StarBlog.Web/wwwroot/media/blog

文章摘要提取

文章摘要提取,我做了簡單的處理,把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);
}
           

邏輯很簡單,判斷标題是否為空(對檔案名來說這不太可能,不過為了嚴謹一點還是做了),然後用正則比對,比對到了就把狀态提取出來,沒比對到就預設

"已釋出"

圖檔提取 & 替換

markdown内容處理比較複雜的就是這部分了,是以我之前就把這部分單獨拿出來寫了一篇文章來介紹,是以本文就不再重複太多,詳情可以看我前面的這篇文章: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解析功能
  • 把所有圖檔連結提取出來
  • 然後根據我們前面在構造方法中傳入的

    importPath

    導入目錄,去拼接圖檔的完整路徑
  • 接着把圖檔複制到

    assetsPath

    裡面
  • 最後把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-tree

關鍵代碼如下,完整代碼在這:https://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);
    }
}
           

用的這個方法叫做“前序周遊”,即先處理目錄下的檔案,然後再處理目錄下的子目錄。

遞歸的方法寫起來比較簡單,但是有一個缺陷是如果目錄結構嵌套太多的話,可能會堆棧溢出,可以考慮換用基于

Stack<T>

模式的周遊,不過作為部落格的目錄層級結構應該不會太多,是以我隻用簡單的~

寫入資料庫

本項目用到的ORM是FreeSQL,ORM操作在後續的網站開發中會有比較多的介紹,是以本文略過,文章資料寫入資料庫的代碼很簡單,可以直接看: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

微信公衆号:「程式設計實驗室」

專注于網際網路熱門新技術探索與團隊靈活開發實踐,包括架構設計、機器學習與資料分析算法、移動端開發、Linux、Web前後端開發等,歡迎一起探讨技術,分享學習實踐經驗。

繼續閱讀