<input id="ohw05"></input>
  • <table id="ohw05"><menu id="ohw05"></menu></table>
  • <var id="ohw05"></var>
  • <code id="ohw05"><cite id="ohw05"></cite></code>
    <label id="ohw05"></label>
    <var id="ohw05"></var>
  • 基于.NetCore開發博客項目 StarBlog - (4) markdown博客批量導入

    系列文章

    前言

    上周介紹了博客的模型設計,現在模型設計好了,要開始導入數據了。

    我們要把一個文件夾內的所有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!

    posted @ 2022-04-30 22:48  程序設計實驗室  閱讀(551)  評論(2編輯  收藏  舉報
    国产美女a做受大片观看