系列文章
- 基於.NetCore開發部落格專案 StarBlog - (1) 為什麼需要自己寫一個部落格?
- 基於.NetCore開發部落格專案 StarBlog - (2) 環境準備和建立專案
- 基於.NetCore開發部落格專案 StarBlog - (3) 模型設計
- 基於.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!
- 部落格後端+前臺專案地址:https://github.com/Deali-Axy/StarBlog
- 管理後臺前端專案地址:https://github.com/Deali-Axy/StarBlog-Admin