当前位置: 首页 > news >正文

# 技术详解: 利用CI同步文章以及多端发布

Image

技术详解: 利用CI同步文章以及多端发布

  • 技术详解: 利用CI同步文章以及多端发布
    • 前言
    • 文章的同步
    • 实现的细节
      • 思路
      • 文章元数据的定义和提取
      • 修改文章的优化
      • 本地图片资源上传CDN并替换本地link
    • 终于到了 CI 的部分了
    • 最后来一些碎碎念

前言

前几天我更新了一篇简单技术总结之后,不少人都对里面的技术细节感兴趣,问我具体是怎么实现的?

于是为了给群友答疑解惑,接下来陆续会聊聊我自己的实现,这篇文章的这个方案已经运行了很多年了,很久没有更新,居然有一天会被人问到,也是比较欣喜的。当然,出于自身水平的限制,目前肯定有更好的方案,所以也欢迎提出建议和意见。

文章的同步

今天先来解答小伙伴第一个小问题,文章同步CI。

目前我实现的效果是,在一个 github私有仓库里,写文章,格式为 markdown,提交到云端后,就自动呈现在我的博客网站和博客小程序里。

同时,如果要对已经存在的文章,进行内容上的修改,或者隐藏和显示,所做的也只需要修改 markdown 文件内容,然后 git commit & push 就可以了。

实现的细节

思路

看到这个方案,下意识就想到了一种实现思路:即我们只需要上传文章内容到数据库,然后在 h5weapp 应用里面,各自装一个适配的 markdown 渲染器,再各自调用后端接口,获取内容直接渲染呈现就行。

于是立即动手开干,先设计数据库,再实现一下后端,最后再写个前端,然后就实现完成了。在实现的时候就会发现,创建一篇文章,并保存到数据库是很容易的,流程无非就是 fs 读取一下内容,提交到 createArticle 接口就完成了。然而,这个方案并没有解决文章的修改和删除问题。

那么,如何让修改后的文章pushremote的时候,也让程序知道是哪一篇文章被修改了,从而进行相应的数据库操作呢?显然基于文章标题或者内容的查询都是不妥的,因为它们都有被修改的可能。

所以这种情况下,我们必须给每一篇文章,设置一个唯一的 UNIQUE ID,这个 UNIQUE ID 不必是数据主键,只需要给它一个唯一性的约束即可。

新的问题接踵而至,这个 UNIQUE ID 应该在哪里进行体现,从而让我们写的文件扫描读取脚本,在获取内容的同时获取到它呢?

文章元数据的定义和提取

这个问题第一眼,我们可能会想到这样的解决方案:这些信息可以体现在文件的名称上呀!比如我们写了一篇文章,文件名叫 如何让霸道富婆爱上我_20230302_520_true.md,我们约定文件名格式为:${title}_${date}_${unique_id}_${valid}.md。这样在扫描的时候,除了获取文件的内容之外,还可以获取到这些信息,再把它们插入到数据库表里,这样似乎就解决了修改和隐藏显示的问题了!

然而这个方案却有一个很大的缺陷,即扩展性很差。这体现在,一旦文件元数据(metadata)多起来,很有可能会出现这样的文件名: 论答辩自产自销造就新时代经济永动机_20230303_666_true_1_999_fuck_your.md,这种文件名称,本身就是一坨答辩。而且每次要修改元数据提交,还会被 git 认为是新创建的文件,显然不好。

所以,我们不应该从文件名提取内容,而应该把这些文章的元数据,放在 md 文件内容中的一块指定区域里,比如放在文章开头。再通过 json/yml 这种格式,来把文章的元数据体现出来。然后再用特殊的分隔线,分隔元数据区域和内容区域。这样我们就可以在代码里,对文件内容块进行各自的处理,元数据区域用 JSON.parse/ js-yaml parse 进行解析,内容区域以字符串形式处理。这有没有让你想到 *.vue 文件的 templatescriptstyle 区域块?

当然这个markdown提取分离方式,我之前找的时候也发现 gray-matter 这个 npm 包可以满足这样的需求。所以推荐使用它,将它加入你的文件扫描脚本中,去提取和分离元数据。

这里也给出一个示例:

---yml
unique_id: 20220330
title: 'icebreaker的垃圾话学习指南'
date: 2022-03-30
description: '不得不学会的垃圾话'
authors: - icebreaker
tags: - 'Trash-talk'
---# icebreaker的垃圾话学习指南...{{content}}...

修改文章的优化

按照上面的做法,我们看似解决了 CRUD 的问题,但是实现后我们会发现,这个做法效率太低了。比如你现在有 1000 篇文章,你改了 500 篇,难道你要让服务端依次把这 1000 篇的文章所有的元数据和内容,一个一个从数据库里取出来,然后和你 git 仓库里的文章,一个字段,一个字段进行比对,然后再把有改动的记录下来,执行 UPDATE 语句?

试想一下,你写了一本 绘声绘色的小黄书 准备发布,好几万个字,发布完了,一分钟后违规通知接踵而至,你需要修改内容。你改完之后,提交发布了,服务器去数据库里,取出原先那巨大的字符串到内存里,再和你提交的另外一坨巨大字符串(也在内存里)做是否相等的判断,而且这个行为还要重复很多次。服务器回复:地铁老人手机.jpg

显然效率太低了,我们需要更精准的做法。所以我们在进行修改文章的时候,不应该拉出这么多的数据去进行比对,而是要充分利用 文件摘要算法 进行效率上的优化。

所以这里我选择 MD5,来对所有的元数据和内容做一个摘要,并且在一开始创建文章的时候,就把 Digest(摘要)存入数据库中,这样在修改的时候,就只要比较摘要是否相等这个结果,相等就跳过,不相等就执行 UPDATE

用代码实现一下,就是:

const klaw = require('klaw')
const matter = require('gray-matter')
const md5 = require('md5')
const normalizeNewline = require('normalize-newline')async function getArticles () {const articles = []for await (const { path: klawPath, stats } of klaw(path.resolve(__dirname, 'path/to/articles'))) {if (path.extname(klawPath) === '.md' && stats.isFile()) {const str = await fs.readFile(klawPath, {encoding: 'utf-8'})const { content, data, orig } = matter(normalizeNewline(str))const digest = md5(orig)articles.push(createArticle({authors: data.authors,// 数据库字段叫 md5 放的就是 digest 摘要md5: digest,content,uniqueId: data.uniqueId,description: data.description,tags: data.tags,title: data.title}))}}return articles
}

这里有一个坑点,为什么需要 normalizeNewline?这源自于 winlinux/unix 这种默认的 EOL(End of Line) 的不同(其实就是老生常谈的\r\n\n问题),这会导致在不同的系统上,文件摘要计算结果的不一致。所以我们需要预先 normalize 一下,这是使用 windows 会遇到的坑点之一,之二是 BOM

接下来要做的,就是把这些文章结果进行分拣,分拣出哪些需要 insert,哪些需要 updatedelete 的。这里可以在服务端写一个同步前的预检接口,返回数据库里文章数据,构建成一个这样结构: Map<uniqueId,md5>,这样就可以把所有本地的文章和这个 Map 进行执行策略上的映射:

  • 假如这个 Map 中,没有当前文章这个 uniqueId, 则意味着这条数据要新增。
  • 假如有这个 uniqueId ,但是这个文章的 valid 标志位被设置为了 false, 则意味着要 softDelete 或者置一个状态的标识位,让它不显示。
  • 假如有这个 uniqueId,且数据是有效的(valid不为false),但是 md5 digest 的值不相等,则意味着这条数据要更新。
  • 否则直接跳过,什么都不做

以上便是本地和远程数据库同步的逻辑。

本地图片资源上传CDN并替换本地link

我们可以用 regex(正则),或者 markdown ast 进行文件内容的匹配提取,匹配 ![alt](href) 成功后,获取后面的本地引用地址,fs 读取之后,上传到你的 oss,然后你的 oss 又关联了 cdn,那对应的 cdn 地址不就有了吗?本地替换一下就行。

推荐有闲钱的同学,可以这样搞。不想花钱的,可以去使用一些免费图床。

终于到了 CI 的部分了

没想到文章的 CRUD 就写了这么多的篇幅,终于到了 Github Action 的配置了,也很简单,核心就执行几段脚本,看我注释就知道都干了啥:

name: 'sync-article'on:# 允许手动触发workflow_dispatch:# 只有 main 分支的 content 下有 md 文件改动,才触发push:branches:- mainpaths:- 'content/**/*.md'jobs:# 同步 jobsync:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v3- uses: actions/setup-node@v3with:# node_modules 缓存 node-version: 16cache: yarncache-dependency-path: 'yarn.lock'- run: yarn --production# 扫描提取仓库文章,然后对数据库进行 CRUD 操作- run: yarn sync# (可选) 重新部署 website 上传到 oss 静态网站并刷新 cdn 缓存- run: yarn website:deploy

最后来一些碎碎念

可能你看到我这篇文章,是在我的小程序上,其实我的小程序因为一些审核的原因,已经快一年没有更新了,同时我也关闭了评论系统,为了内容安全。

还有获取文章内容的接口,一定要用 token 保护好,入参也尽量使用uuid这种无法找到规律的字符串,不然很容易就被爬接口一下子全爬下来了(当然直接抓html还是可以的,你可以放一些垃圾在里面)。

还有,假如你使用的是那种,nextjs/nuxtjs 里一些基于文件系统的CMS npm包(比如@nuxtjs/content),然后你还要开源的话,你应该把你的文章放入另外一个私有仓库,然后通过git submodule 把它添加进来,接着在运行和部署的时候,通过软连接,放入这类内容CMS包的指定文件夹中,这点 Github Action 也是能做到的。

      - name: Checkout Self Repouses: actions/checkout@v3with:submodules: 'true'token: ${{ secrets.PAT }}

相关文章:

# 技术详解: 利用CI同步文章以及多端发布

技术详解: 利用CI同步文章以及多端发布 技术详解: 利用CI同步文章以及多端发布 前言文章的同步实现的细节 思路文章元数据的定义和提取修改文章的优化本地图片资源上传CDN并替换本地link 终于到了 CI 的部分了最后来一些碎碎念 前言 前几天我更新了一篇简单技术总结之后&am…...

分形维数的计算方法汇总

以下是常用的时间序列分形维数计算方法及相应的参考文献&#xff1a;Hurst指数法Hurst指数法是最早用于计算分形维数的方法之一&#xff0c;其基本思想是通过计算时间序列的长程相关性来反映其分形特性。具体步骤是&#xff1a;(1) 对原始时间序列进行标准化处理。(2) 将序列分…...

微积分小课堂:积分(从微观趋势了解宏观变化)

文章目录 引言I. 预备知识: 积分效应1.1 闯黄灯1.2 公司利润(飞轮效应)1.3 飞轮效应II 积分2.1 积分的计算2.2 积分思想的本质引言 微分解决的问题是从宏观变化了解微观趋势;积分和微分刚好相反,是从微观去看宏观变化。 通过积分效应,提升我们的认识水平,同时能用一些工…...

4道数学题,求解极狐GitLab CI 流水线|第4题:合并列车

本文来自&#xff1a; 武让 极狐GitLab 高级解决方案架构师 &#x1f4a1; 极狐GitLab CI 依靠其一体化、轻量化、声明式、开箱即用的特性&#xff0c;在开发者群体中的使用率越来越高&#xff0c;在国内企业中仅次于 Jenkins &#xff0c;排在第二位。 极狐GitLab 流水线有 4…...

代码规范简述

目录 命名规范 代码格式 OOP规约 集合规范 并发规范 SQL语句规范 SQL 建表规范 SQL 索引规范 SQL 查询规范 控制语句规范 Javadoc 规范 其他规范 命名规范 1、包名&#xff1a;使用小写字母&#xff0c;多个单词之间用"."分隔&#xff0c;例如&#xff…...

【Java集合框架】篇五:Map接口

1. Map及实现类特点 Map&#xff1a;存储key-value HashMap&#xff1a;线程不安全&#xff0c;效率高&#xff0c;key和value都可以为null&#xff0c;底层使用 数组单向链表红黑树 结构&#xff08;jdk8&#xff09;。 LinkedHashMap&#xff1a;是HashMap的子类&#xff0…...

Typroa安装教程

Markdown 是一种轻量级标记语言&#xff0c;创始人为约翰格鲁伯&#xff08;John Gruber&#xff09;。 它允许人们使用易读易写的纯文本格式编写文档&#xff0c;然后转换成有效的 XHTML&#xff08;或者HTML&#xff09;文档。这种语言吸收了很多在电子邮件中已有的纯文本标记…...

【MySQL】存储引擎

目录 1.MySQL体系结构 2.存储引擎介绍 3.存储引擎特点 4.存储引擎选择 1.MySQL体系结构 MySQL整体的逻辑结构可以分为4层,客户层、服务层、存储引擎层、数据层 客户层 客户层:进行相关的连接处理、权限控制、安全处理等操作 服务层 服务层负责与客户层进行连接处理、处…...

芯驰(E3-gateway)开发板环境搭建以及调试遇到问题的解决

1-Windows下环境配置 可以在Windows上使用命令行或者IAR IDE编译SSDK项目。Windows编译依赖的工具已经包含在 prebuilts/windows 目录中&#xff0c;包括编译器、Python和命令行工具。 1.1.1 CMD SSDK集成 msys 工具&#xff0c;可以在Windows命令行中完成SDK的配置、编译和…...

【大数据监控】Prometheus、Node_exporter、Graphite_exporter安装部署详细文档

目录Prometheus简介下载软件包安装部署创建用户创建Systemd服务修改配置文件prometheus.yml启动Prometheusnode exporter下载软件包安装部署添加用户创建systemd服务启动node_exportergraphite_exporter下载软件包安装部署创建systemd服务启动 graphite_exporterPrometheus 简介…...

《C++ Primer》 第十一章 关联容器

《C Primer》 第十一章 关联容器 11.1 使用关联容器 使用map: //统计每个单词在输入中出现的次数 map<string, size_t> word_count;//string到size_t的空map string word; while(cin>>word)word_count[word];//提取word的计数器并将其加1 for(const auto &w:…...

WebRTC标准与框架解读(1)

1、如果让我来设计webrtc框架我在分析源码的时候&#xff0c;都喜欢做这样一件事情&#xff1a;如果让我来设计它&#xff0c;我会怎么做&#xff1f;大家可以紧跟我的思路&#xff0c;分析一下WebRTC为什么如此设计。为了对整个框架有有一个全面的了解&#xff0c;我们首先要做…...

数据结构的一些基础概念

一 基本术语 数据&#xff1a;是描述客观事物的符号&#xff0c;是计算机中可以操作的对象&#xff0c;是能被计算机识别&#xff0c;并输入给计算机处理的符号集合。 数据元素&#xff1a;是组成数据的&#xff0c;有一定意义的基本单位&#xff0c;在计算机中通常作为整体处…...

【Python每日一练】总目录(不断更新中...)

Python 2023.03 20230303 1. 两数之和 ★ 2. 组合总和 ★★ 3. 相同的树 ★★ 20230302 1. 字符串统计 2. 合并两个有序链表 3. 下一个排列 20230301 1. 只出现一次的数字 2. 以特殊格式处理连续增加的数字 3. 最短回文串 Python 2023.02 20230228 1. 螺旋矩阵 …...

latex插入图片(自用)

加入宏包&#xff1a;\usepackage{graphicx} 使用 \includegraphics 命令进行插图。 \includegraphics[]{}&#xff1a; 第一参数[]&#xff1a;对图片做一些适当的调整&#xff08;设定图片的高度和宽度或者按比例缩放&#xff09; 第二参数{}&#xff1a;图片的名字&#xf…...

【微信小程序】-- 网络数据请求(十九)

&#x1f48c; 所属专栏&#xff1a;【微信小程序开发教程】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &…...

K8S 实用工具之一 - 如何合并多个 kubeconfig?

开篇 &#x1f4dc; 引言&#xff1a; 磨刀不误砍柴工工欲善其事必先利其器 K8S 集群规模&#xff0c;有的公司倾向于少量大规模 K8S 集群&#xff0c;也有的公司会倾向于大量小规模的 K8S 集群。 如果是第二种情况&#xff0c;是否有一个简单的 kubectl 命令来获取一个 kubec…...

阿里云ECS服务器的6大功能组件

阿里的云服务在国内可以说是首屈一指的了&#xff0c;因此他们家的云服务器也是最受欢迎的。那么&#xff0c;你知道阿里云服务器ECS有哪些功能组件吗&#xff1f;不清楚不要紧&#xff0c;下面服务器吧小编带大家来看看。 在了解之前我们来看一张阿里云服务器ECS的产品组件架…...

外贸建站多少钱?不同预算对应的建站方案!

外贸建站多少钱&#xff1f; 答案是&#xff1a;3000左右。 作为一个外贸企业的经营者&#xff0c;我们深知一个优质的外贸网站对于企业的重要性。 然而&#xff0c;建立一个优质的外贸网站需要耗费大量的时间和资金&#xff0c;因此我们需要在预算有限的情况下&#xff0c;…...

Vue3中hook的使用及使用中遇到的坑

目录前言一&#xff0c;什么是hook二&#xff0c; hook函数的使用2.1 铺垫2.2 hook函数的写法2.3 使用写好的hook函数后记前言 在学习Es6的时候&#xff0c;我们开始使用类与对象&#xff0c;开始模块化管理&#xff1b;在Vue中我们可以使用mixin进行模块化管理&#xff1b;Vu…...

数据库-差集交集并集

数据库-差集交集并集[toc]图示一、并集运算&#xff08;UNION&#xff09;并集&#xff1a;两个集合的并集是一个包含集合A和B中所有元素的集合。在T-SQL中&#xff0c;UNION集合运算可以将两个输入查询的结果组合成一个结果集。需要注意的是&#xff1a;如果一个行在任何一个输…...

spark性能调优(四):网络

网络 一、数据读写二、数据处理三、数据传输在平衡不同硬件资源的时候,相比于CPU、内存、磁盘,网络开销处理延迟最高 一、数据读写 对于大多数应用来说,第一步都是从分布式系统中读取数据,不论什么文件格式,也不管哪种文件存储系统,访问数据源是否会引入网络开销,取决于任务与…...

高性能 WPF 图表控件LightningChart.NET:支持从 Web 服务器获取数据 | 附最新版试用下载

LightningChart.NET 是一款高性能 WPF 和 Winforms 图表,可以实时可视化多达1万亿个数据点。可有效利用CPU和内存资源&#xff0c;实时监控数据流。同时&#xff0c;LightningChart使用突破性创新技术&#xff0c;以实时优化为前提&#xff0c;大大提升了实时渲染的效率和效果&…...

文科女生月入14k背后:转行IT软件测试不是谁都学得来!

转行软件测试背后&#xff0c;或许每个人都有自己的无奈。就拿今天要和大家分享的这位小姐姐来说吧&#xff0c;如果不是万不得已&#xff0c;又怎么会狠下心来转行到IT互联网&#xff1f; 应届生逃避就业&#xff0c;考研失败 和大多数人一样&#xff0c;小姐姐的大学生活过得…...

GB28181监控视频统一汇聚平台LiveGBS将海康大华华为宇视等厂家监控设备统一接入后如何生成固定播放链接或者固定的流地址可以直接无插件播放或者拉取

目前汇聚各种厂家监控设备的视频汇聚平台&#xff0c;基本都是通过GB28181标准协议实现的。下面介绍下LiveGBS Web无插件直播的GB28181视频平台将各厂家&#xff08;包括海康、大华、华为、宇视、天地伟业等&#xff09;监控汇聚到同一个服务器上后&#xff0c;如何或者直播链接…...

认识BUG

如何描述 bug一个合格的 bug 描述应该包括以下几个部分&#xff1a;发现问题的版本开发人员需要知道出现问题的版本&#xff0c;才能够获取对应版本的代码来重现故障&#xff0c;并且版本的标识也有利于统计和分析每个版本的质量。问题出现的环境环境分为硬件环境和软件环境&am…...

C++string类型内置的搜索函数

string的搜索操作 string类型一共提供了6种不同的搜索函数&#xff0c;每个函数都有4个重载版本。如果搜索成功&#xff0c;每个搜索操作都会返回一个 string::size_type类型的值&#xff0c;表示匹配发生位置的下标。 如果搜索失败&#xff0c;则会返回一个名位string::npos…...

Disruptor 消费线程丢失、写入无限阻塞问题

使用jstack打印后&#xff0c;发现 1&#xff09;写入disruptor线程卡死在 java.lang.Thread.State: TIMED_WAITING (parking) at sun.misc.Unsafe.park(Native Method) at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:338) at com.lmax.…...

【禅道测试环境搭建及安装】Linux上的禅道安装教程,从环境搭建开始

目录 一、操作环境 二、安装VMware 三、安装FinalShell 四、在VMware里安装CentOS 1.前置&#xff1a;CentOS的下载 2.CentOS的安装 3.查看网络 五、用宿主机连接远程的虚拟机 六、禅道包的下载与CentOS配置的修改 七、上传项目包并安装禅道 一、操作环境 Windows 10…...

spring-boot rabbitmq整合

文章请参考&#xff1a;Springboot 整合RabbitMq &#xff0c;用心看完这一篇就够了 mven依赖 <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></depende…...

舞钢市城乡建设局网站/电子商务

Linux安装ftp组件 1 安装vsftpd组件 安装完后&#xff0c;有/etc/vsftpd/vsftpd.conf文件&#xff0c;是vsftp的配置文件。 [python] view plaincopy [rootbogon ~]# yum -y install vsftpd 2 添加一个ftp用户 此用户就是用来登录ftp服务器用的。 [ruby] view plaincopy [ro…...

wordpress做的网站/邢台市seo服务

视频链接 一.:课程说明(1~1)...

个人电子邮件注册网站申请/市场监督管理局电话

下载安装 官网下载安装&#xff1a;https://www.sublimetext.com/Package Control&#xff1a;https://packagecontrol.io/官方文档&#xff1a;https://www.sublimetext.com/docs/index.html非官方文档&#xff08;有很多对官方文档的说明&#xff09;&#xff1a;https://do…...

wordpress搜索功能加强/直播回放老卡怎么回事

PHP文章摘要生成方法(函数)文章生成摘要的方法有多种&#xff0c;可以用JS在客户端生成&#xff0c;也可以在服务器端生成&#xff0c;当然更不排除在数据库中加一个摘要字段&#xff0c;在发布文章的时候自行设置。以下是在服务器端生成时的方法。我们在写BLOG时经常需要显示文…...

网址导航网站建设/廊坊自动seo

PIM 文件疑难解答常见的 PIM 打开问题Avid Pro Tools 不在你尝试加载 PIM 文件并收到错误&#xff0c;例如 “%%os%% 无法打开 PIM 文件扩展名”。 发生这种情况时&#xff0c;通常是由于 %%os%% 中缺少 Avid Pro Tools。 通过双击打开 PIM 的典型路径将不起作用&#xff0c;因…...

网站建设开发原代码归属/怎么从网上找客户

TIOBE编程语言社区排行榜是编程语言流行趋势的一个指标&#xff0c;每月更新&#xff0c;这份排行榜排名基于互联网上有经验的程序员、课程和第三方厂商的数量。排名使用著名的搜索引擎&#xff08;诸如Google、MSN、Yahoo!、Wikipedia、YouTube以及Baidu等&#xff09;进行计算…...