一开始博客使用的 Halo,发现问题比较多啊,时不时的莫名其妙主题各种报错,有时候还要升级,麻烦的要死,于是就想弄简单点。
这两天抽空反复倒腾了一遍,不小心还把镜像给尼玛删了,发的文章都没了,痛定思痛,要做改变!
众所周知,我懒出名了,我觉得这种事情你不要老是让我操心啊,最好是一年都不要动一下才好,这搞的跟什么一样。
研究一会儿,最终还是决定 docsify+github 来弄,初步的想法是本地写好 MD 文件直接 push 到 github上,然后触发 github 的webhook,触发脚本去 pull 代码到服务器上。

博客
这样的话还有点想象空间,以后可以省去一大部分同步文章的工作,都可以出发回调去通过 API 同步,不过暂时还没有调研这些平台是否能支持,不过应该问题不大。
试试解决方案可不可行吧,我觉得很 nice。
docsify 搭建安装
首先安装 docsify-cli 工具
然后进入自己的目录,初始化
这样就差不多了,多了几个文件,简单修改一下 index.html,配置下名字和代码仓库的信息,开启下左边的侧边栏。
同时补充一点插件,都从网上搂的。
然后运行,本地会启动 http://localhost:3000,直接看看效果
大概就这个样子了

侧边栏还没有,使用命令生成一下,会自动帮我们根据目录创建一个_sidebar.md
文件,也就是我们的侧边栏了。
然后再看看效果怎么样,差不多就这样,需要注意的是文件名不能有空格,否则生成的侧边栏目录会有问题,需要修改一下文件名使用中划线或者下换线替换空格(我无法理解为什么不能用空格)。

多级目录生成问题
另外还有一个问题是无法生成多级目录,也就是不能超过二级目录,这个我自己随便改了一下。
去 docsify-cli 官方 git 仓库下载源码,然后把这个丢进去,在目录下执行 node generate.js
就可以,底部测试目录改成自己的目录。
'use strict'
const fs = require('fs')
const os = require('os')
const {cwd, exists} = require('../util')
const path = require('path')
const logger = require('../util/logger')
const ignoreFiles = ['_navbar', '_coverpage', '_sidebar']
// eslint-disable-next-line
function test (path = '', sidebar) {
// 获取当前目录
const cwdPath = cwd(path || '.')
// console.log('cwdPath', cwdPath, !!exists(cwdPath));
// console.log('///////', cwdPath, path, cwd(path || '.'))
if (exists(cwdPath)) {
if (sidebar) {
const sidebarPath = cwdPath + '/' + sidebar || '_sidebar.md';
if (!exists(sidebarPath)) {
genSidebar(cwdPath, sidebarPath)
logger.success(`Successfully generated the sidebar file '${sidebar}'.`)
return true
}
logger.error(`The sidebar file '${sidebar}' already exists.`)
process.exitCode = 1
return false
}
return false;
}
logger.error(`${cwdPath} directory does not exist.`)
}
let tree = '';
function genSidebar(cwdPath, sidebarPath) {
// let tree = '';
let lastPath = ''
let nodeName = ''
let blankspace = '';
let test = 0;
const files = getFiles(cwdPath);
console.log(JSON.stringify(files));
getTree(files);
fs.writeFile(sidebarPath, tree, 'utf8', err => {
if (err) {
logger.error(`Couldn't generate the sidebar file, error: ${err.message}`)
}
})
return;
getDirFiles(cwdPath, function (pathname) {
path.relative(pathname, cwdPath) // 找cwdPath的相对路径
pathname = pathname.replace(cwdPath + '/', '')
let filename = path.basename(pathname, '.md') // 文件名
let splitPath = pathname.split(path.sep) // 路径分割成数组
let blankspace = '';
if (ignoreFiles.indexOf(filename) !== -1) {
return true
}
nodeName = '- [' + toCamelCase(filename) + '](' + pathname + ')' + os.EOL
if (splitPath.length > 1) {
if (splitPath[0] !== lastPath) {
lastPath = splitPath[0]
tree += os.EOL + '- ' + toCamelCase(splitPath[0]) + os.EOL
}
tree += ' ' + nodeName
// console.error('tree=====', tree, splitPath, splitPath.length);
} else {
if (lastPath !== '') {
lastPath = ''
tree += os.EOL
}
tree += nodeName
}
})
fs.writeFile(sidebarPath, tree, 'utf8', err => {
if (err) {
logger.error(`Couldn't generate the sidebar file, error: ${err.message}`)
}
})
}
function getFiles (dir) {
// let path = require('path');
// let fs = require('fs');
let rootDir = dir;
var filesNameArr = []
let cur = 0
// 用个hash队列保存每个目录的深度
var mapDeep = {}
mapDeep[dir] = 0
// 先遍历一遍给其建立深度索引
function getMap(dir, curIndex) {
var files = fs.readdirSync(dir) //同步拿到文件目录下的所有文件名
files.map(function (file) {
//var subPath = path.resolve(dir, file) //拼接为绝对路径
var subPath = path.join(dir, file) //拼接为相对路径
var stats = fs.statSync(subPath) //拿到文件信息对象
// 必须过滤掉node_modules文件夹
if (file != 'node_modules') {
mapDeep[file] = curIndex + 1
if (stats.isDirectory()) { //判断是否为文件夹类型
return getMap(subPath, mapDeep[file]) //递归读取文件夹
}
}
})
}
getMap(dir, mapDeep[dir])
function readdirs(dir, folderName, myroot) {
var result = { //构造文件夹数据
path: dir,
title: path.basename(dir),
type: 'directory',
deep: mapDeep[folderName]
}
var files = fs.readdirSync(dir) //同步拿到文件目录下的所有文件名
result.children = files.map(function (file) {
//var subPath = path.resolve(dir, file) //拼接为绝对路径
var subPath = path.join(dir, file) //拼接为相对路径
var stats = fs.statSync(subPath) //拿到文件信息对象
if (stats.isDirectory()) { //判断是否为文件夹类型
return readdirs(subPath, file, file) //递归读取文件夹
}
if (path.extname(file) === '.md') {
const path = subPath.replace(rootDir + '/', '');
// console.log(subPath, rootDir, '========', path);
return { //构造文件数据
path: path,
name: file,
type: 'file',
deep: mapDeep[folderName] + 1,
}
}
})
return result //返回数据
}
filesNameArr.push(readdirs(dir, dir))
return filesNameArr
}
function getTree(files) {
for (let i=0; i<files.length;i++) {
const item = files[i];
if (item) {
if (item.deep === 0) {
if (item.children) {
getTree(item.children)
}
} else {
let blankspace = ''
for (let i = 1; i < item.deep; i++) {
blankspace += ' '
}
// console.log('-' + blankspace + '-', item.deep)
if (item.type === 'directory') {
tree += os.EOL + blankspace + '- ' + toCamelCase(item.title) + os.EOL
} else if (item.type === 'file') {
tree += os.EOL + blankspace + '- [' + item.name + '](' + item.path + ')' + os.EOL
// console.log('tree', tree);
}
if (item.children) {
getTree(item.children)
}
}
}
}
}
function getDirFiles(dir, callback) {
fs.readdirSync(dir).forEach(function (file) {
let pathname = path.join(dir, file)
if (fs.statSync(pathname).isDirectory()) {
getDirFiles(pathname, callback)
} else if (path.extname(file) === '.md') {
callback(pathname)
}
})
}
function toCamelCase(str) {
return str.replace(/\b(\w)/g, function (match, capture) {
return capture.toUpperCase()
}).replace(/-|_/g, ' ')
}
test("/Users/user/Documents/JavaInterview/", "sidebar.md");
- 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.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
- 129.
- 130.
- 131.
- 132.
- 133.
- 134.
- 135.
- 136.
- 137.
- 138.
- 139.
- 140.
- 141.
- 142.
- 143.
- 144.
- 145.
- 146.
- 147.
- 148.
- 149.
- 150.
- 151.
- 152.
- 153.
- 154.
- 155.
- 156.
- 157.
- 158.
- 159.
- 160.
- 161.
- 162.
- 163.
- 164.
- 165.
- 166.
- 167.
- 168.
- 169.
- 170.
- 171.
- 172.
- 173.
- 174.
- 175.
- 176.
- 177.
- 178.
- 179.
- 180.
- 181.
- 182.
- 183.
- 184.
- 185.
- 186.
- 187.
- 188.
- 189.
- 190.
- 191.
- 192.
- 193.
- 194.
- 195.
- 196.
- 197.
- 198.
- 199.
这样的话一切不就都好起来了吗?看看最终的效果。

nginx 配置
webhook 暂时还没弄,先手动 push 上去然后把文件都传到服务器上去,再设置一下 nginx 。
这时候你去 reload nginx 会发现网站可能 403 了,把你的 ng 文件第一行改了,甭管后面是啥,改成 root 完事儿。
这样就 OK 了。
内网穿透
然后,就需要搞一个 webhook 的程序了,但是在此之前,为了本地能测试 webhook 的效果,需要配置个内网穿透的程序,我们使用 ngrok 。
先安装。
然后需要注册一个账号,这里没关系,直接 google 登录就好了,之后进入个人页面会提示你使用步骤。
按照步骤来添加 token,然后映射 80 端口。

如果后面发现 ngrok 命令找不到,可以去官网手动下一个文件,丢到/usr/local/bin
目录中就可以了。
成功之后可以看到 ngrok 的页面,使用他给我们提供的 Forwarding 地址,就是我们的公网地址。
我随便弄了个 80 端口,这里用自己到时候项目的端口号映射就行了,后面我们改成 9000。

webhook
配置好之后,就去我们的 github 项目设置 webhook,使用上面得到的地址。

这样就设置OK了,然后搞个 Java 程序去,监听 9000 端口,随便写个 Controller
。
然后随便改点我们的文档,push 一下,收到了 webhook 的回调,其实我们根本不关注回调的内容,我们只要收到这个通知,就去触发重新拉取代码的指令就行。
接着,我们实现代码逻辑,根据回调执行命令去拉取最新的代码到本地,为了能通过 Java 操作 Git,引入 JGit。
为了简单,我每次都是重新 clone 仓库下来,正常来说应该第一次 clone,后面直接 pull 代码,这里为了省事儿,就先这样操作。
代码执行后已经是 OK 了,可以直接拉取到代码,那么至此,差不多已经 OK 了,后面把代码直接丢服务器上去跑着就拉倒了。

服务器的问题
好了,你以为到这里就结束了吗?年轻了,年轻了。。。
这代码丢到服务器上跑会发现报错连不上 github,如果你是阿里云服务器的话。
解决方案是找到 /etc/ssh/ssh_config
,删掉 GSSAPIAuthentication no 这行前面的注释,然后保存,你才会发现真的是能下载了。
同时,nginx 我们映射另外一个域名作为回调的域名,这里需要主要以下你的 https 证书,因为我是免费版的,所以 https 这个域名无法生效,那 webhook 回调注意用 http 就行。
OK,如果你用上面的方式还无法解决,可以换一种方式,直接通过 Java 直接 shell 脚本去处理。
用脚本的目的是可以用加速的 git 地址,否则服务器访问经常会失败,这个找个插件就可以有加速地址了。

代码上传的问题
好了,这样通过手动上传代码的方式其实已经可以用了,但是为了部署更方便一点,我建议安装一个插件Alibaba Cloud Toolkit
,其他云服务器有的也有这种类型的插件。

安装好插件之后会进入配置的页面,需要配置一个accessKey
。

去自己的阿里云账号下面配置。

配置好了之后,点击Tools-Deploy to xxx
,我是 ECS ,所以选择 ECS 服务器即可。

然后在这里会自动加载出你的服务器信息,然后选择自己的部署目录,同时选择一个 Command 也就是执行命令。

这个命令随便找个目录创建一个这个脚本,放那里就行了,最后点击 Run,代码就飞快的上传了。
结束
基本的工作已经做完了,那其实还有挺多细节问题没有处理的,比如失败重试、回调鉴权等等问题,这只是一个非常初级的版本。
同步代码 git 地址:https://github.com/irwinai/sync-tools
文章仓库地址:https://github.com/irwinai/JavaInterview
博客地址:https://aixiaoxian.vip/#/
文章转载自公众号:艾小仙