基於.NetCore開發部落格專案 StarBlog

2022-10-07 06:01:44

前言

九月太忙,只更新了三篇文章,本來這個功能是從九月初就開始做的,結果一直拖到現在國慶假期才有時間完善並且寫文章~

之前我更新了幾篇關於 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() 就能解壓了,但如果壓縮包裡的檔案用了中文名,就得先設定編碼。

解析Markdown

關於 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);

系列文章

參考資料