基于.NetCore开发博客项目 StarBlog - (17) 自动下载文章里的外部图片

系列文章

  • 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客?

  • 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目

  • 基于.NetCore开发博客项目 StarBlog - (3) 模型设计

  • 基于.NetCore开发博客项目 StarBlog - (4) markdown博客批量导入

  • 基于.NetCore开发博客项目 StarBlog - (5) 开始搭建Web项目

  • 基于.NetCore开发博客项目 StarBlog - (6) 页面开发之博客文章列表

  • 基于.NetCore开发博客项目 StarBlog - (7) 页面开发之文章详情页面

  • 基于.NetCore开发博客项目 StarBlog - (8) 分类层级结构展示

  • 基于.NetCore开发博客项目 StarBlog - (9) 图片批量导入

  • 基于.NetCore开发博客项目 StarBlog - (10) 图片瀑布流

  • 基于.NetCore开发博客项目 StarBlog - (11) 实现访问统计

  • 基于.NetCore开发博客项目 StarBlog - (12) Razor页面动态编译

  • 基于.NetCore开发博客项目 StarBlog - (13) 加入友情链接功能

  • 基于.NetCore开发博客项目 StarBlog - (14) 实现主题切换功能

  • 基于.NetCore开发博客项目 StarBlog - (15) 生成随机尺寸图片

  • 基于.NetCore开发博客项目 StarBlog - (16) 一些新功能 (监控/统计/配置/初始化)

  • 基于.NetCore开发博客项目 StarBlog - (17) 自动下载文章里的外部图片

前言

好久没更新博客了,上个月底更新了一篇关于StarBlog博客开发的文章之后,就因为线下培训、诗词大会之类的杂七杂八的事浪费了很多时间,有段时间一直在忙这些事情都没空写代码……

PS:我在诗词大会上分享了这首诗:读白居易的《禽虫十二章》

然后最近买了杨中科大佬新出的《AspNetCore技术内幕》,看得津津有味,花了一个多星期的时间,把书里的内容大致看了一遍,DDD(领域驱动设计)我早就想学了,不过一直没找到好的入门资料,大佬的这本书就很不错,很好懂,尽管如此,DDD还是一个相对复杂的方法,需要通过不断的实践来掌握。

虽然最近做了这么多事,但同时工作也很忙,有个项目需要在九月前上线,本来我打算来实践一下DDD的,不过写着写着发现还是把握不住,只好先用我之前的DjangoStarter框架,后面再慢慢把我的StarBlog博客用DDD思想进行改造~

对了,这么久没更新博客的原因,还有一点是我在使用过程中对目前的管理后台非常不满(使用Vue2+ElementUI开发),用户体验极差,所以我同时在构思用何种技术对管理后台前端项目进行重构,目前有几个备选项:

  • blazor(使用C#开发前端,很酷)

  • react(相对其他的来说,我最喜欢的前端技术栈)

  • 仍然vue,但重写现有架构(工作量较小)

还没拿定主意,在重构完成之前,只能先捏着鼻子用现有的管理后台,同时大概率也不会在现有的前端项目中增加新功能了。

回到正题

OK,说回本文的内容。在博客的使用过程中,有时候我会从其他网站复制一些markdown片段,或者是从我在其他平台的博客上复制markdown内容(博客园、掘金之类的),这时候复制过来的markdown内容里面可能会有一些图片,如果不做处理,可能会产生某些问题,如因图片防盗链功能导致网络图片在StarBlog博客中无法显示、网站运营商关闭导致图片丢失等,对于数据,还是牢牢掌握在自己的手中比较放心。

于是,我就做了这个功能:将markdown文章中的网络图片下载下来,并且替换markdown中的链接

原理很简单,扫描markdown,把图片链接拿出来下载,同时把图片链接替换成StarBlog上的地址。下面一步步介绍如何在代码中实现。

下载图片

首先是下载图片的功能,C#中访问网络,可以使用HttpClient这个标准库

最简单的用法是这样:

var client = new HttpClient();
await client.GetAsync("图片地址");

不过官方文档中并不推荐这种用法,最佳实践是一个程序中只维护一个HttpClient的对象

在AspNetCore中,我们可以利用依赖注入IHttpClientFactory来管理HttpClient对象。

Program.cs中注册服务

builder.Services.AddHttpClient();

在需要的地方注入IHttpClientFactory,比如在本项目中,我们新建一个CommonService.cs来放下载文件的代码,考虑到这个功能以后别的地方也可能用到,所以做成通用的,不和PostService耦合在一起。

代码如下:

public class CommonService {private readonly ILogger<CommonService> _logger;private readonly IHttpClientFactory _httpClientFactory;public CommonService(ILogger<CommonService> logger, IHttpClientFactory httpClientFactory) {_logger = logger;_httpClientFactory = httpClientFactory;}public async Task<string?> DownloadFileAsync(string url, string savePath) {var httpClient = _httpClientFactory.CreateClient();try {var resp = await httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);// 生成随机文件名var fileName = GuidUtils.GuidTo16String() + Path.GetExtension(url);var filePath = Path.Combine(savePath, WebUtility.UrlEncode(fileName));await using var fs = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write);await resp.Content.CopyToAsync(fs);return fileName;}catch (Exception ex) {_logger.LogError("下载文件出错,信息:{Error}", ex);return null;}}
}

分析一下部分代码:

  • 第13行代码使用HttpClient的GetAsync方法下载数据,添加了个HttpCompletionOption.ResponseHeadersRead参数,这样我们不必等全部信息加载到内存中后再进行流读取之类的操作,而是在请求头返回的时候就可以进入下一步处理。避免因为要下载的文件太大而导致OutOfMemoryException,这对下载文件的程序来说很重要!

  • 第16行,使用封装好的Guid工具生成16位的GUID,直接用Guid.NewGuid().ToString()也行,这是32位的。

  • 第18-19行,将Http响应内容写入文件流

搞定,下载文件代码比较简单,涉及到IO操作这种容易出错的地方,细节要处理好,才能保证程序的稳定性。

PS:别忘了注册服务!

builder.Services.AddSingleton<CommonService>();

处理Markdown

下载图片的功能搞定了之后,我们继续来做markdown处理的部分

关于C#处理Markdown,之前已经有过多次探索了,可以说是轻车熟路了hhh~

附上之前关于Markdown处理的文章:

  • C#解析Markdown文档,实现替换图片链接操作

  • 基于.NetCore开发博客项目 StarBlog - (4) markdown博客批量导入

依然是用Markdig这个库(貌似.NetCore处理markdown上也没其他选择)

PostService.cs中增加代码

/// <summary>
/// Markdown中外部图片下载
/// <para>如果Markdown中包含外部图片URL,则下载到本地且进行URL替换</para>
/// </summary>
private async Task<string> MdExternalUrlDownloadAsync(Post post) {if (post.Content == null) return string.Empty;// 得先初始化目录InitPostMediaDir(post);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;var imgUrl = linkInline.Url;// 跳过空链接if (imgUrl == null) continue;// 跳过本站地址的图片if (imgUrl.StartsWith(Host)) continue;// 下载图片_logger.LogDebug("文章:{Title},下载图片:{Url}", post.Title, imgUrl);var savePath = Path.Combine(_environment.WebRootPath, "media", "blog", post.Id!);var fileName = await _commonService.DownloadFileAsync(imgUrl, savePath);linkInline.Url = fileName;}}await using var writer = new StringWriter();var render = new NormalizeRenderer(writer);render.Render(document);return writer.ToString();
}

代码说明:

  • 第9行的初始化目录就是检查这篇文章有没有对应的目录,没有就先创建,很简单就不贴代码了。可以在github项目里看到完整代码

  • 第12行开始的两层循环通过遍历markdown文档树,把图片链接找出来

  • 第22行检查图片是站外还是站内的,站内图片不用下载

这样就完成了markdown里站外图片的下载和链接替换~

修改文章保存逻辑

接下来修改一下文章的保存逻辑

还是在这个PostService.cs里,保存和新增文章共享一个方法:InsertOrUpdateAsync

直接上代码

public async Task<Post> InsertOrUpdateAsync(Post post) {// 是新文章的话,先保存到数据库if (await _postRepo.Where(a => a.Id == post.Id).CountAsync() == 0) {post = await _postRepo.InsertAsync(post);}// 检查文章中的外部图片,下载并进行替换post.Content = await MdExternalUrlDownloadAsync(post);// 修改文章时,将markdown中的图片地址替换成相对路径再保存post.Content = MdImageLinkConvert(post, false);// 处理完内容再更新一次await _postRepo.UpdateAsync(post);return post;
}

代码说明:

  • 新文章的话,会先保存一次,作为草稿。

  • 先下载外部图片,再替换本地图片链接(关于图片链接替换的,可以参考本系列第4篇文章,上面有链接)

  • 完成这些之后再保存,注意这时文章还是草稿状态,需要通过另一个方法将文章的IsPublish属性设置为true,不过与本文关系不大,这里先不贴代码,后续在RESTFul接口开发部分的文章里会详细介绍这个流程。

到这里就搞定啦~

参考资料

  • 官方文档:https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=net-6.0

  • c#:HttpClient使用详解:https://blog.csdn.net/u010476739/article/details/119782562

  • C#中HttpClient的使用小结:https://zhuanlan.zhihu.com/p/89106847

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/283624.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

windbg工具安装配置及dump抓取

安装与配置windbg 安装与配置windbg的symbol(符号) 第一步 下载WinDBG&#xff0c; 第二步 双击下载的文件安装windbg.安装时注意记住安装到那里了. 第三步 windbg访问符号需要两个文件(SYMSRV.DLL 和 SYMSTORE.EXE)所以在环境变量path中将windbg安装目录添加进去&#xff0c;这…

三种Oracle RMAN备份加密策略(下)

说明&#xff1a;本篇参考eygle老师的作品《Oracle DBA手记4&#xff1a;数据安全警示录》&#xff0c;特此表示感谢。 3 、Oracle Wallet加密策略Oracle Wallet是一种加密安全策略&#xff0c;过去我们在TDE&#xff08;Oracle透明加密&#xff09;部分研究过这个组件。简单的…

实现生成订单30分钟未支付,则自动取消

目录 了解需求 方案 1&#xff1a;数据库轮询 思路 实现 优点 缺点 方案 2&#xff1a;JDK 的延迟队列 思路 实现 优点 缺点 方案 3&#xff1a;时间轮算法 思路 实现 优点 缺点 方案 4&#xff1a;redis 缓存 思路一 实现一 解决方案 思路二 实现二 优…

Oracle树形结构查询之prior的理解

--1 建表 create table 宇宙( 行星等级 number ,行星名称 varchar2(50) ,上级行星等级 number); --2 数据准备 insert into 宇宙 (行星等级, 行星名称, 上级行星等级)values (1, 地球, 2); insert into 宇宙 (行星等级, 行星名称, 上级行星等级)values (2, 太阳, 3); insert in…

CA周记-.NET MAUI in GCR 月报(2022年8月)

.NET MAUI 正式版本发布已经三个月了&#xff0c;有小伙伴希望我们有一些关于 .NET MAUI 相关的本地化内容以及开源项目介绍&#xff0c;接下来从8月开始&#xff0c;我希望用月报的形式和大家分享 .NET MAUI 在中国的活动&#xff0c;学习资源&#xff0c;优秀的开源项目&…

Vue的内容分发slot的使用

什么是内容分发&#xff1f;? 概括&#xff1a;将父组件的内容放到子组件指定的位置 场景&#xff1a;在使用组件时&#xff0c;我们常常需要像这样组合使用 < app>< app-header>< /app-header>< app-footer>< /app-footer> < /app> 复制…

一文读懂研发效能洞察的五大流动指标

作者 | 张乐 目录 1 数字化时代&#xff0c;软件研发本身也要数字化 2 流框架及五大流动指标 1. 流动速率 2. 流动时间 3. 流动负载 4. 流动效率 5. 流动分布 3 研发过程中的常见瓶颈及解决思路 1. 稀缺的专家或资源&#xff0c;导致流动受阻 2. 缺乏自动化或工程能…

RabbitMQ队列

RabbitMQ是什么&#xff1f; RabbitMQ是一个在AMQP基础上完整的&#xff0c;可复用的企业消息系统。他遵循Mozilla Public License开源协议。 MQ全称为Message Queue, 消息队列&#xff08;MQ&#xff09;是一种应用程序对应用程序的通信方法。应用程序通过读写出入队列的消息&…

《ASP.NET Core 6框架揭秘实例》演示[14]:日志的进阶用法

为了对各种日志框架进行整合&#xff0c;微软创建了一个用来提供统一的日志编程模式的日志框架。《ASP.NET Core 6框架揭秘》实例演示[13]&#xff1a;日志的基本编程模式》以实例演示的方式介绍了日志的基本编程模式&#xff0c;现在我们来补充几种“进阶”用法。[本文节选《A…

Linux内核驱动GPIO的使用

一 概述Linux内核中gpio是最简单&#xff0c;最常用的资源(和 interrupt ,dma,timer一样)驱动程序&#xff0c;应用程序都能够通过相应的接口使用gpio&#xff0c;gpio使用0&#xff5e;MAX_INT之间的整数标识&#xff0c;不能使用负数,gpio与硬件体系密切相关的,不过linux有一…

什么是云原生,云原生技术为什么这么火?

文章目录 一、开篇浅谈二、云计算是什么三、云原生是什么四、云计算的四个层次 4.1 IaaS&#xff08;基础架构即服务&#xff09;4.2 PaaS&#xff08;平台即服务&#xff09;4.3 SaaS&#xff08;软件即服务&#xff09;4.4 DaaS&#xff08;数据即服务&#xff09;五、云原生…

Html5学习笔记1 元素 标签 属性

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>HTML5学习笔记</title> </head><body bgcolor"#90ee90">1.Html5的元素<br/><br/>元素指的是从開始标签到结束…

PerfView专题 (第五篇):如何寻找 C# 托管内存泄漏

一&#xff1a;背景 前几篇我们聊的都是 非托管内存泄漏&#xff0c;这一篇我们再看下如何用 PerfView 来排查 托管内存泄漏 &#xff0c;其实 托管内存泄漏 比较好排查&#xff0c;尤其是用 WinDbg&#xff0c;毕竟C#是带有丰富的元数据&#xff0c;不像C下去就是二进制。二&a…

DevOps及DevOps常用的工具介绍

目录 1. 什么是 DevOps2. DevOps 概念的起源 2.1. 单体架构 瀑布模式2.2. 分布式架构 敏捷开发模式 2.2.1. 多人协同开发问题2.2.2. 多机器问题2.2.3. 开发和运维角色的天生对立问题2.3. 微服务架构 DevOps3. DevOps 到底是什么4. DevOps 常用的工具 4.1. Jenkins4.2. Kuber…

2018年SIAF 广州国际工业自动化技术及装备展览会下周隆重开幕

同期研讨活动聚焦行业未来趋势&#xff0c;探索技术发展及实际应用层面。 华南最重要的工业自动化行业盛会之一&#xff0c;SIAF广州国际工业自动化技术及装备展览会&#xff0c;将于2018年3月4至6日在广州中国进出口商品交易会展馆隆重开幕。为期三天的展会将再度与广州国际模…

生活感言

There will be moments when the only thing left is for us to question our existence.总时有一些瞬间&#xff0c;让人感觉留给我们的唯一的事情就是质疑我们自身的存在。There are always days when we get so lonely and depressed and the world has lost its colours.我…

相约现在,遇见未来

# 遇见未来这个世界很小&#xff0c;我们就这样遇见。这个世界很大&#xff0c;分开就很难再见。大家好&#xff0c;我是 chait&#xff0c;很高兴我们在这里《遇见》。今天是我申请公众号通过后的第一天&#xff0c;也是在该平台发表的第一篇文章&#xff0c;唠嗑点啥呢&#…

2018-04-12

https://www.cnblogs.com/dragonsuc/p/5512797.html linux top https://www.cnblogs.com/kex1n/p/7211008.html linux 后台 http://dev.xxzhushou.cn/noticeArticle.html?ID65 chacha http://bbs.xxzhushou.cn/forum.php?modviewthread&tid88904141&extrapage%3D1%26…

有关并行的两个重要定律

本文摘自 葛一鸣 老师的《实战java高并发程序设计》一书。因为觉得写得好就摘下来了 将串行程序改造成并发程序&#xff0c;一般来说可以提高程序的整体性能&#xff0c;但是究竟能提升多少&#xff0c;甚至说究竟是否真的可以提高&#xff0c;还是一个需要研究的问题。目前&am…

java web中jsp常用标签

在jsp页面开发过程中&#xff0c;经常需要使用JSTL&#xff08;Java Server Pages Standard Tag Library&#xff09;标签开开发页面&#xff0c;是看起来更加的规整舒服。 JSTL主要提供了5大类标签库:1. 核心标签库: 为日常任务提供通用支持,如显示和设置变量,重复使用一…