博客的自动发布流程

从 2014 年年初写博客至今,四年多过去了。折腾了好些博客系统,也折腾了好些编辑器,发现博客的发布流程长短,严重影响着博客更新的频率与质量。鉴于此,谈谈我目前博客的发布流程是怎样的,欢迎讨论。

环境信息
node v9.8.0
ruby 2.5.0


在程序员的圈子里,不仅用什么语言要吵,用什么框架要吵,用什么编辑器要吵,用什么博客系统也要吵。众说纷纭当中,如果能找到适合自己的,也不枉摸鱼一天。

就拿写博客和 Markdown 编辑器(或者是笔记这类 App)来说,我用过以下几款:

  • Wordpress,OneThink,Jekyll,Octopress,Hexo 等
  • EverNote,AlterNote,Mou,MacDown,MWeb,Quiver,SnippetsLab,Bear,SublimeText,Atom,VSCode,Typora 等

其实,每换一次这些工具也好,系统也好,成本都非常高。这些应用或多或少会有些不满足需求,要不然就多个套用,互相弥补,要不然就砍掉自己的需求,适应某一个,当然,作为开发者,也可以选择自己写一个。

问题分析

从工具的过渡不难看出,博客系统再从「富文本 + 动态网站」逐渐变为「Markdown + 静态网站」。而编辑器也在从「零散 + App 自带云存储 + 编辑与预览双屏」到「纯文本编辑 + 独立存储 + 单屏」转变。目前我在用的几款应用如下:

  • Hexo
  • Bear,VSCode,Typora

博客不用多谈,编辑器之所以有三种,是因为他们的用途不同,并不是一款无法满足要求。

  • Bear:主力摘抄
  • VSCode:主力写代码,偶尔查看 Markdown 原格式
  • Typora:主力 Markdown 编辑器

而备份,清一色的 Git。Bear 在出来的很长一段时间,我都不太喜欢。虽然界面让人眼前一亮,但是备份只能是 Bear 自己的格式,而且它的 tag 是用 #,这和 Markdown 原有标签冲突,导出就会很丑,无法平滑迁移。但是它的同步、UI,以及 tag 的归类方式,真的挺好用,所以用来做摘抄了。

当我习惯 Markdown,但仍在用 Wordpress 发布的时候,发现 WP 对 Markdown 支持并不好,解析也会出现乱码。所以,我当时发布一篇博客,流程很长:

  1. 用 Typora 完成博客编写
  2. 压缩、上传图片到三方图床,替换相对路径
  3. 导出为不带样式的 HTML
  4. 登录 Wordpress 后台,复制粘贴富文本
  5. 设置文章描述、分类、关键字等,点击发布

经过在各种系统上的迁移后,发现博客万变不离其宗的几个特点,这也是在迁移过程中遇到的问题:

  1. 富文本 or Markdown?(富文本需要依赖渲染,而 Markdown 可读性更高)
  2. 动态 or 静态?(动态对站内检索支持更方便,如果实在需要,静态也不是不能实现)
  3. 自己搭 or 三方博客系统?(三方博客…基本上数据就被绑架了,以后别想换)
  4. ​博客系统特定格式 or Markdown 原生格式?(如果用一种博客就改一种格式,多痛苦)

需求确定

对于以上问题,我更倾向于用纯 Markdown,并且就用原生的格式,当然,这其中还需要考虑信息的融合。举个 🌰。

从 Wordpress 中,可以导出的信息是最完整的,除以下信息外,还有发布时间、分类、标签、作者、固定链接等等。

<title>标题<title>
<meta name="description" content="描述">
<meta name="keywords" content="关键字">

而 Hexo 支持的格式为:

title: 标题
categories: 分类
tags: 标签
description: 描述
keywords: 关键字
date: 发布时间
permalink: 固定链接
---

原生 Markdown 的信息最少:

# 标题

描述

<!--more-->

这就非常尴尬了,原生也太不友好了。那么还要用原生的吗?要!原生是对各个平台支持最好的,以后要迁移起来,也很方便。哪怕最后选择直接 push GitHub,显示起来也没问题。对此,可以对文件目录以及内容做些改动(比如当前这篇博客):

- 2018-09-05-design-a-singleton-block-callbak
- 2018-09-07-efforts-will-not-be-wasted
- 2018-10-15-publish-blog-automatically
- index.md
- publish-blog-automatically-1.png
- publish-blog-automatically-2.png
- other-source.key

这样,日期与固定链接,就放到了文件夹名称当中,而且文章当中,可以以相对路径的形式引用图片,基本上可以沉浸式的写作,不用关心图片的压缩和上传这些问题。

而其他的信息,都放在了文章内部:

# 标题

<cate hidden>分类</cate>
<tags hidden>标签</tags>
<keys hidden>关键字</keys>

描述。

<!--more-->

文章中,采用 hidden 的方式,将附加信息补充上去,并不影响查看。而描述,则是在 more 标签之前。讲道理,more 之前本来也代表着表述。至此,原生的 Markdown 已经可以基本满足日常书写需求了。想要缩短发布的流程,还需要一些脚本的支持。

当然,大家写博客的时候,可能没这么复杂。我的之所以会比较麻烦,是因为最初是富文本,然后用七牛做的图床(辣鸡七牛,别用),私有 Repo,也不想用 GitHub Pages。除了发布博客以外,还会导出一下其他数据。整体来看,对原始数据的处理很多,干脆就自己写个流程了。

针对之前遇到过的问题,整理了一下目标:

  1. 专注写作,图片的压缩和上传服务器自动处理
  2. 博客的发布完全不用关注,本地只管 push
  3. 在发布之前,校验博客的格式以及必要参数,将错误前置
  4. 以前的发布脚本只针对有改动的文章处理,有很多漏洞,维护成本高
  5. 支持多个平台的发布

动手实现

首先,将资源分为了四类,放到了四个 repo 里面。除图床外,其他三个 repo 都是 private 的。

其中最为关键的,就是服务器的脚本。说来也不难,就是平时发博客需要手动处理的那几个步骤:

  • 拉取原始文件 repo,处理图片并上传到图床。将原始文件中的图片相对路径,改为图床中的路径
  • 读取基础信息,比如分类,关键字等,生成 Hexo 可用的格式
  • 写个简单的服务,用于接收 repo 变更,push 时触发的 webhook

Rake

用 Rake 写了三个 Task:

desc "发布博客"
task :publish do |cmd, args|
# 拉去原始 markdown 文件夹
Dir[original_folder].each do |f|
# 遍历处理图片,合成单个文件
end

# 遍历 Markdown 文件
Dir[single_md_folder].each do |md|
# 转为 Hexo 格式
end

# 执行 hexo generate

# 拷贝静态 HTML 到固定路径,由 Nginx 代理
end

desc "处理 Nginx"
task :nginx do |cmd, args|
# 本地和服务器的 nginx 配置不同,所以 nginx 的处理是独立的,只是为了方便
end

desc "重启 rack server"
task :rack do |cmd, args|
# 重启 rack
end

Rack

其中,发布脚本随便用什么写都行。Webhook 的接收,我采用了 Rack 做服务,一个文件搞定。

require 'rack'

class SaiBlog
def call(env)
req = Rack::Request.new(env)
if req.post? && env["REQUEST_PATH"] == "/deploy"
system "rake publish"
end
[200, {"Content-Type" => "application/json"}, []]
end
end

Rack::Handler::WEBrick.run(SaiBlog.new, :Port => 9222)

当被监听的 repo 有改动时,触发 Webhook,执行 rake publish,然后根据 publish 的脚本,自动生成静态文件。

Docker

整个服务器的环境,都做成了 Docker 镜像,方便处理服务器脚本与环境更新。

Slack

由于太穷,而且懒,就没有集成各种 CI 平台,从目前来看,三方平台值得一用的就只是日志查看而已。鉴于此,决定集成 Slack,用于部署结果以及全量日志的查看入口,而且桌面与移动端都能查看。

集成的流程很简单,看文档就基本能搞定。部署脚本需要导出日志,并且上传,最后一步就是发送消息了。

log_path = "/Users/xxx/logs/20181015101010.log"
publish_error = system "set -o pipefail && rake publish | tee #{log_path}"
# 上传运行日志
upload_error, log_url = upload(log_path)
# 推送消息
msg = []
msg << "---------"
msg << "📃 博客部署第 #{File.basename(log_path)}"
msg << (publish_error ? "😭 部署失败,日志放到最后了~" : "🎉 部署成功")
msg << (upload_error ? "😭 日志上传失败 - #{upload_error}" : "全量日志链接 - #{log_url}")
msg << "---------" if upload_error
msg << publish_error if upload_error
push(msg.join("\n"))

效果大概是这样的:

最后

折腾这么久,整个部署流程其实还有可以优化的地方:

  1. 图片的压缩的还是应该本地处理,毕竟除了图床,原始文件的 repo 里面还有一份呢,会导致这个 repo 越来越大。
  2. 不带 alpha 通道的图片完全可以处理为 JPG。

欢迎大家分享自己的流程,一起讨论。