九月太忙,只更新了三篇文章,本來這個功能是從九月初就開始做的,結果一直拖到現在國慶假期才有時間完善並且寫文章~
之前我更新了幾篇關於 Python 的文章,有朋友留言問是不是不更新 .Net 了,那肯定不能啊,我只能說「我 全 都 要」,所以我反手就更新了一篇Asp-Net-Core開發筆記。
然後順便立個Flag:今年底前完成StarBlog系列文章的主體部分(即API開發+後臺前端開發,目前只完成部落格前後端部分),加油吧~
OK,說回本文,程式設計師都喜歡用Markdown來寫文章,但由於markdown是純文字格式,在其中插入的圖片要如何儲存,就成了一大煩惱,有人選擇圖床,但不一定永久有效;有人選擇本地儲存,圖片永久有效,但如何分享文章又成了一個難題…
我選的就是第二種,本地儲存。使用Typora寫文章,圖片儲存在和Markdown檔案同名的目錄(markdown.assets)下,這樣可以獲得很好的寫作體驗,然後分享的問題就交給StarBlog吧,這個專案開發的初衷就是為了把原生的文章發表成部落格。
不過之前只有批次匯入文章的功能,現在我要做的就是單獨實現一個單篇文章打包匯入的功能。
隨著文章越來越多,系列文章的目錄放前面有點影響閱讀了,所以從這篇開始我把它放到最後面~
假設我用Typora寫了一篇Markdown文章,檔名為:StarBlog.md
,並且在裡面插入了若干圖片,根據設定,Typora會自動生成一個目錄(StarBlog.assets
)來存放這些圖片。
為了實現匯入,我要把這個markdown檔案和這個存圖片的目錄一起打包成zip壓縮檔案上傳,後端將zip壓縮包解壓到臨時目錄,讀取Markdown檔案,解析其中的內容,進行匯入操作。
OK,開始寫程式碼吧
同時所有專案程式碼已經上傳GitHub,歡迎各位大佬Star/Fork!
首先是解壓縮功能,.Net標準庫自帶 ZipFile
這個庫用於操作zip壓縮包,在 System.IO.Compression
裡,直接用就完事了。
解壓前得先把檔案複製到臨時目錄,並建立一個新的臨時目錄來放解壓後的檔案。
在 Services/BlogServices.cs
裡新增程式碼
public async Task<Post> Upload(PostCreationDto dto, IFormFile file) {
// 先複製到臨時檔案
var tempFile = Path.GetTempFileName();
await using (var fs = new FileStream(tempFile, FileMode.Create)) {
await file.CopyToAsync(fs);
}
// 設定解壓用的臨時目錄
var extractPath = Path.Combine(Path.GetTempPath(), "StarBlog", Guid.NewGuid().ToString());
// 使用 GBK 編碼解壓,防止中文件名亂碼
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
ZipFile.ExtractToDirectory(tempFile, extractPath, Encoding.GetEncoding("GBK"));
}
本來直接 ZipFile.ExtractToDirectory()
就能解壓了,但如果壓縮包裡的檔案用了中文名,就得先設定編碼。
關於 C# 解析 Markdown ,在本系列一開始就寫過,所以這裡就不再當復讀機了,可以直接看這兩篇文章:
直接上程式碼了
因為是做單篇文章匯入,所以我這裡獲取臨時目錄寫的所有 *.md
檔案之後只取第一個檔案來處理(理論上也不應該有多個~)
var dir = new DirectoryInfo(extractPath);
var files = dir.GetFiles("*.md");
var mdFile = files.First();
using var reader = mdFile.OpenText();
var content = await reader.ReadToEndAsync();
var post = new Post {
Id = GuidUtils.GuidTo16String(),
Status = "已釋出",
Title = dto.Title ?? $"{DateTime.Now.ToLongDateString()} 文章",
IsPublish = true,
Content = content,
Path = "",
CreationTime = DateTime.Now,
LastUpdateTime = DateTime.Now,
CategoryId = dto.CategoryId,
};
var assetsPath = Path.Combine(_environment.WebRootPath, "media", "blog");
var processor = new PostProcessor(extractPath, assetsPath, post);
// 處理文章標題和狀態
processor.InflateStatusTitle();
// 處理文章正文內容
// 匯入文章的時候一併匯入文章裡的圖片,並對圖片相對路徑做替換操作
post.Content = processor.MarkdownParse();
post.Summary = processor.GetSummary(200);
Markdown相關的處理,我封裝了 PostProcessor
這個物件,在 StarBlog.Share
裡。
如果文章的分類不是一級分類,那麼把它上面的所有分類找出來,一個個排好隊,方便後面處理。
// 處理多級分類
var category = await _categoryRepo.Where(a => a.Id == dto.CategoryId).FirstAsync();
if (category == null) {
post.Categories = "0";
}
else {
var categories = new List<Category> {category};
var parent = category.Parent;
while (parent != null) {
categories.Add(parent);
parent = parent.Parent;
}
categories.Reverse();
post.Categories = string.Join(",", categories.Select(a => a.Id));
}
搞定~
// 存入資料庫
post = await _postRepo.InsertAsync(post);