基于.NetCore开发博客项目 StarBlog - (4) markdown博客批量导入
系列文章
- 基于.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
基于.NetCore开发博客项目 StarBlog - (4) markdown博客批量导入的更多相关文章
- 基于.NetCore开发博客项目 StarBlog - (19) Markdown渲染方案探索
前言 笔者认为,一个博客网站,最核心的是阅读体验. 在开发StarBlog的过程中,最耗时的恰恰也是文章的展示部分功能. 最开始还没研究出来如何很好的使用后端渲染,所以只能先用Editor.md组件做 ...
- 基于.NetCore开发博客项目 StarBlog - (5) 开始搭建Web项目
系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...
- 基于.NetCore开发博客项目 StarBlog - (6) 页面开发之博客文章列表
系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...
- 基于.NetCore开发博客项目 StarBlog - (7) 页面开发之文章详情页面
系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...
- 基于.NetCore开发博客项目 StarBlog - (8) 分类层级结构展示
系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...
- 基于.NetCore开发博客项目 StarBlog - (9) 图片批量导入
系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...
- 基于.NetCore开发博客项目 StarBlog - (10) 图片瀑布流
系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...
- 基于.NetCore开发博客项目 StarBlog - (11) 实现访问统计
系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...
- 基于.NetCore开发博客项目 StarBlog - (12) Razor页面动态编译
系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...
随机推荐
- 哪一个List实现了最快插入?
LinkedList和ArrayList是另个不同变量列表的实现.ArrayList的优势在于动态的增长数组,非常适合初始时总长度未知的情况下使用.LinkedList的优势在于在中间位置插入和删除操 ...
- 可以直接调用 Thread 类的 run ()方法么?
当然可以.但是如果我们调用了 Thread 的 run()方法,它的行为就会和普通的方 法一样,会在当前线程中执行.为了在新的线程中执行我们的代码,必须使用 Thread.start()方法.
- 简述 Mybatis 的插件运行原理,以及如何编写一个插件。
Mybatis 仅可以编写针对 ParameterHandler.ResultSetHandler. StatementHandler.Executor 这 4 种接口的插件,Mybatis 使用 J ...
- 直接使用sublime编译stylus
stylus介绍 Stylus 是一个CSS的预处理框架,2010年产生,来自Node.js社区,主要用来给Node项目进行CSS预处理支持,所以 Stylus 是一种新型语言,可以创建健壮的.动态的 ...
- 前端面试题整理——webpack相关考点
webpack是开发工具,面试考点重点在配置和使用,原理理解不需要太深. 一.基本配置 1.拆分配置和merge 将公共配置跟dev和prod的配置拆分,然后通过webpack-merge对配置进行整 ...
- 我的python学习记_02
流程控制 算术运算符: + 加(在字符串中拼接作用) - 减 * 乘 / 除 // 商 % 取余 ** 次幂 比较运算符: > 是否大于 >= 是否大于等于 < 是否小于 != 是否 ...
- Android:Unable to find explicit activity class报错
错误:Unable to find explicit activity class 原因:没有给activity在AndroidManifest.xml中注册 解决办法: 在AndroidManife ...
- 小程序中webview内嵌h5页面
小程序内嵌h5页面跳转小程序指定页面, 需要引用 JSSDK: <script src="https://res.wx.qq.com/open/js/jweixin-1.3.2 ...
- <!--[if IE]><style></style><![endif]-->
CSS hack就是为了让你的CSS代码兼容不同的浏览器,其中最难对付的就是IE浏览器的兼容性,因为它的版本很多,不过还好,微软发表声明已经不对IE8以下的版本进行维护了.但是我们也不能就认为不用学I ...
- YC-Framework版本更新:V1.0.6
分布式微服务框架:YC-Framework版本更新V1.0.6!!! 本文主要内容: V1.0.6版本更新主要内容 V1.0.6版本更新主要内容介绍 一.V1.0.6版本更新主要内容 1.系统例子覆盖 ...