部署好博客之后,我们就可以发布博文了。由于Hexo是静态博客,每次发布文章后均需要手动输入命令来部署博客。博主觉得十分麻烦,能不能用什么方法实现上传文章后自动部署?简单搜索后,发现了一些现成的工具。但博主喜欢自己动手,何况这个功能的原理应该很简单。所以决定自己造轮子了。本文将介绍博主写的自动部署工具。有需要的朋友可以参考修改也可以直接使用。
Node.js
首先就是选择使用的语言。由于博客部署在Ubuntu,是Linux环境,能方便使用的语言是Shell本身和Python,外加安装Hexo之前必须安装的Node.js,一共是三种语言可以选择。博主Shell命令用的多,但是很少编写Shell程序,排除。Python博主也很喜欢,但是由于最近实习需要每天使用Nodej.js,所以更倾向于使用Node.js。之后如果有时间会更新Python和Shell版本的自动发布工具。 不打算更新了,有更好的轮子:Hexo-Auto-Deployer
原理
再来看看手动发布的流程。
- 上传
.md
文件到_post
文件夹下(hexo new
命令本质就是使用模板创建.md
文件) - 使用命令
hexo d -g
生成并发布文章
第一步的上传文件目前还是采取sftp上传。下一步考虑自动从OneDrive同步。
第二步是本篇的重点。这一步可以分为两部分。
那么边看代码边讲解吧。
监听文件夹
如何监听_post
文件夹内是否有新文件呢?首先想到的是使用setInterval
函数轮询文件夹内的所有文件,然后比对上一次查询到的最后修改时间mTime
。如果不一致则代表修改过;新增和删除可以通过是否存在旧/新纪录来判断。但这个方法有个主要的缺点是轮询的间隔不好设置,过短对服务器有压力,过长则不能及时更新。谁也不想刚写完的文章要等好久才能看到效果吧,那还不如直接手动发布。
所以翻看fs
模块的文档,找到了一个叫fs.watch
的函数。这个函数刚好符合我的需求:通过事件监听机制及时获取文件变动状态。以下是第一步的监听文件夹的代码:
1 2 3 4 5 6 7 8 9
| const fs = require('fs'); const path = require('path');
fs.watch(path.resolve(__dirname, 'blog', 'source', '_posts'), (event, filename) => { if (filename) { console.log(`Changes may happen on file ${filename}`); } });
|
非常简单明了。watch
接受一个参数一个回调函数。参数是监听的目录,这里通过path.resolve
来拼接目录地址,其中__dirname
是当前工作目录。回调函数提供两个参数,event
表示事件的类型,本工具暂时不做区分,所以不需要使用;filename
是触发事件的文件名。
关键点
运行上面这段代码做测试,添加或修改文件,每次操作都会有输出,但是次数却不止一次。这是因为有些操作,比如写入,有些编辑器会先清空旧文件再写入,因此多次触发监听事件。其次根据平台的不同,有些操作会有中间态,持续几十到几百毫秒不等,因此也会多次触发监听事件。
这个问题不细想可能觉得没问题,但是仔细思考会发现这样做有些隐患。显而易见的是浪费资源,发布一个文章可能反复部署两三次。因此,需要对以上代码进行修改。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const fs = require('fs'); const path = require('path');
let blogWait = false; fs.watch(path.resolve(__dirname, 'blog', 'source', '_posts'), (event, filename) => { if (filename) { if (blogWait) return; blogWait = setTimeout(() => { blogWait = false; console.log(`Changes may happen on file ${filename}`); }, 200); } });
|
我们可以加入一个信号量wait
,默认为false
,首次触发监听事件的时候设置一个延迟操作,在200ms后再次将wait
信号量设置为false
。同时把wait
设置为setTimeout
的返回值,由于返回值是一个大于0的数字,在判断时会被当做true
来处理。整个操作实现的是:在首次监听到变化时,改变信号量,即防止后续监听到变化时运行核心代码,并在200ms后恢复正常。由于200ms是一个相对于手动操作来说比较短的时间,一般也不会出现负面效果。
发布文章
发布文章需要用的子进程模块,导入包之后可以直接运行Shell命令,比如运行ll
命令:
1 2 3 4 5 6 7 8 9 10
| const { exec } = require('child_process');
exec('ls -l', (error, stdout, stderr) => { if (error) { console.error(error); return; } console.log(stdout); console.error(stderr); });
|
其中error
是报错,stdout
和stderr
正常输出和错误输出,自动发布工具不涉及,所以不展开。
最后把部署文章的代码加到监听的代码中,直接来看总效果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const fs = require('fs'); const path = require('path'); const { exec } = require('child_process');
let blogWait = false; fs.watch(path.resolve(__dirname, 'blog', 'source', '_posts'), (event, filename) => { if (filename) { if (blogWait) return; blogWait = setTimeout(() => { blogWait = false; console.log(`${filename} file Changed`); exec('hexo d -g', { cwd: path.resolve(__dirname, 'blog'), }, (err) => { if(err) { console.log('Cannot deploy Hexo'); } else { console.log('Blog Deployed'); } }) }, 200); } });
|
exec
接受的第二个参数是选项,由于Hexo需要在博客根目录下运行部署命令,因此添加一个cwd
选项切换工作目录。不直接把部署工具放在博客根目录下是因为本站是双语站,需要监听两个目录。如果你要需要部署双语站,可以参考:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| const fs = require('fs'); const path = require('path'); const { exec } = require('child_process');
let blogWait = false; fs.watch(path.resolve(__dirname, 'blog', 'source', '_posts'), (event, filename) => { if (filename) { if (blogWait) return; blogWait = setTimeout(() => { blogWait = false; console.log(`${filename} file Changed`); exec('hexo d -g', { cwd: path.resolve(__dirname, 'blog'), }, (err) => { if(err) { console.log('Cannot deploy Hexo'); } else { console.log('Blog Deployed'); } }) }, 200); } });
let zhBlogWait = false; fs.watch(path.resolve(__dirname, 'zhblog', 'source', '_posts'), (event, filename) => { if (filename) { if (zhBlogWait) return; zhBlogWait = setTimeout(() => { zhBlogWait = false; console.log(`${filename} file Changed`); exec('hexo d -g', { cwd: path.resolve(__dirname, 'zhblog'), }, (err) => { if(err) { console.log('Cannot deploy Hexo'); } else { console.log('ZhBlog Deployed'); } }) }, 200); } });
|