一开始博客应用的 Halo,发现问题比拟多啊,时不时的莫名其妙主题各种报错,有时候还要降级,麻烦的要死,于是就想弄简略点。

这两天抽空重复倒腾了一遍,不小心还把镜像给尼玛删了,发的文章都没了,痛定思痛,要做扭转!

家喻户晓,我懒闻名了,我感觉这种事件你不要老是让我操心啊,最好是一年都不要动一下才好,这搞的跟什么一样。

钻研一会儿,最终还是决定 docsify+github 来弄,初步的想法是本地写好 MD 文件间接 push 到 github上,而后触发 github 的webhook,触发脚本去 pull 代码到服务器上。

这样的话还有点设想空间,当前能够省去一大部分同步文章的工作,都能够登程回调去通过 API 同步,不过临时还没有调研这些平台是否能反对,不过应该问题不大。

试试解决方案可不可行吧,我感觉很 nice。

docsify 搭建装置

首先装置 docsify-cli 工具

npm i docsify-cli -g

而后进入本人的目录,初始化

docsify init ./

这样就差不多了,多了几个文件,简略批改一下 index.html,配置下名字和代码仓库的信息,开启下右边的侧边栏。

同时补充一点插件,都从网上搂的。

<!DOCTYPE html><html lang="en"><head>  <meta charset="UTF-8">  <title>Document</title>  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />  <meta name="description" content="Description">  <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">  <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify@4/lib/themes/vue.css"></head><body>  <div id="app"></div>  <script>    window.$docsify = {      name: '艾小仙',      repo: 'https://github.com/irwinai/JavaInterview.git',      loadSidebar: true,      autoHeader: true,      subMaxLevel: 3,      sidebarDisplayLevel: 1, // set sidebar display level      count:{        countable:true,        fontsize:'0.9em',        color:'rgb(90,90,90)',        language:'chinese'      },      search: {        maxAge: 86400000, // Expiration time, the default one day        paths: [], // or 'auto'        placeholder: '开始搜寻',        noData: '啥也没有!'      },      copyCode: {        buttonText : '点击复制',        errorText  : '谬误',        successText: '复制胜利'      },      footer: {        copy: '<span>艾小仙 &copy; 2022</span>',        auth: '赣ICP备2021008029号-1',        pre: '<hr/>',        style: 'text-align: right;',        class: 'className'      }    }  </script>  <!-- Docsify v4 -->  <script src="//cdn.jsdelivr.net/npm/docsify@4"></script>  <!-- 搜寻 -->  <script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/search.min.js"></script>  <!-- 图片放大放大反对 -->  <script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/zoom-image.min.js"></script>  <!-- 拷贝文字内容 -->  <script src="//cdn.jsdelivr.net/npm/docsify-copy-code/dist/docsify-copy-code.min.js"></script>  <!-- 字数插件 -->  <script src="https://cdn.jsdelivr.net/npm/docsify-count@latest/dist/countable.min.js"></script>  <!-- 分页导航 -->  <script src="//unpkg.com/docsify-pagination/dist/docsify-pagination.min.js"></script>  <!-- 侧边栏扩大与折叠 -->  <script src="//cdn.jsdelivr.net/npm/docsify-sidebar-collapse/dist/docsify-sidebar-collapse.min.js"></script>  <!-- 页脚信息 -->  <script src="//unpkg.com/docsify-footer-enh/dist/docsify-footer-enh.min.js"></script>  <!-- GitTalk评论 -->  <!-- <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/gitalk/dist/gitalk.css">  <script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/gitalk.min.js"></script>  <script src="//cdn.jsdelivr.net/npm/gitalk/dist/gitalk.min.js"></script> --></body></html>

而后运行,本地会启动 http://localhost:3000,间接看看成果

docsify serve

大略就这个样子了

侧边栏还没有,应用命令生成一下,会主动帮咱们依据目录创立一个_sidebar.md文件,也就是咱们的侧边栏了。

docsify generate .

而后再看看成果怎么样,差不多就这样,须要留神的是文件名不能有空格,否则生成的侧边栏目录会有问题,须要批改一下文件名应用中划线或者下换线替换空格(我无奈了解为什么不能用空格)。

多级目录生成问题

另外还有一个问题是无奈生成多级目录,也就是不能超过二级目录,这个我本人轻易改了一下。

去 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-linefunction 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");

这样的话所有不就都好起来了吗?看看最终的成果。

nginx 配置

webhook 临时还没弄,先手动 push 下来而后把文件都传到服务器下来,再设置一下 nginx 。

server {    listen 80;    listen [::]:80;    server_name aixiaoxian.vip;    client_max_body_size 1024m;    location / {        root /你的目录;        index index.html;    }}server {    listen       443 ssl;    server_name  aixiaoxian.vip;    root         /usr/share/nginx/html;    ssl_certificate cert/aixiaoxian.pem;    ssl_certificate_key cert/aixiaoxian.key;    ssl_session_cache shared:SSL:1m;    ssl_session_timeout  10m;    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;    ssl_prefer_server_ciphers on;    ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #示意应用的TLS协定的类型。    include /etc/nginx/default.d/*.conf;    location / {        root /你的目录;        index index.html;    }}

这时候你去 reload nginx 会发现网站可能 403 了,把你的 ng 文件第一行改了,甭管前面是啥,改成 root 完事儿。

user root;

这样就 OK 了。

内网穿透

而后,就须要搞一个 webhook 的程序了,然而在此之前,为了本地能测试 webhook 的成果,须要配置个内网穿透的程序,咱们应用 ngrok 。

先装置。

brew install ngrok/ngrok/ngrok

而后须要注册一个账号,这里没关系,间接 google 登录就好了,之后进入集体页面会提醒你应用步骤。

依照步骤来增加 token,而后映射 80 端口。

如果前面发现 ngrok 命令找不到,能够去官网手动下一个文件,丢到/usr/local/bin目录中就能够了。

胜利之后能够看到 ngrok 的页面,应用他给咱们提供的 Forwarding 地址,就是咱们的公网地址。

我轻易弄了个 80 端口,这里用本人到时候我的项目的端口号映射就行了,前面咱们改成 9000。

webhook

配置好之后,就去咱们的 github 我的项目设置 webhook,应用下面失去的地址。

这样就设置OK了,而后搞个 Java 程序去,监听 9000 端口,轻易写个 Controller

@PostMapping("/webhook")public void webhook(@RequestBody JsonNode jsonNode) {    System.out.println("json===" + jsonNode);}

而后轻易改点咱们的文档,push 一下,收到了 webhook 的回调,其实咱们基本不关注回调的内容,咱们只有收到这个告诉,就去触发从新拉取代码的指令就行。

{    "repository": {        "id": 306793662,        "name": "JavaInterview",        "full_name": "irwinai/JavaInterview",        "private": false,        "owner": {            "name": "irwinai",            "email": "415586662@qq.com",            "login": "irwinai"        },        "html_url": "https://github.com/irwinai/JavaInterview",        "description": null,        "fork": false    },    "pusher": {        "name": "irwinai",        "email": "415586662@qq.com"    },    "sender": {        "login": "irwinai",        "id": 4981449    }}

接着,咱们实现代码逻辑,依据回调执行命令去拉取最新的代码到本地,为了能通过 Java 操作 Git,引入 JGit。

<dependency>  <groupId>org.eclipse.jgit</groupId>  <artifactId>org.eclipse.jgit</artifactId>  <version>5.13.1.202206130422-r</version></dependency>

为了简略,我每次都是从新 clone 仓库下来,失常来说应该第一次 clone,前面间接 pull 代码,这里为了省事儿,就先这样操作。

@RestControllerpublic class WebhookController {    private static final String REMOTE_URL = "https://github.com/irwinai/JavaInterview.git";    @PostMapping("/webhook")    public void webhook(@RequestBody JsonNode jsonNode) throws Exception {        File localPath = new File("/Users/user/Downloads/TestGitRepository");        // 不论那么多,先删了再说        FileUtils.deleteDirectory(localPath);        //间接 clone 代码        try (Git result = Git.cloneRepository()                .setURI(REMOTE_URL)                .setDirectory(localPath)                .setProgressMonitor(new SimpleProgressMonitor())                .call()) {            System.out.println("Having repository: " + result.getRepository().getDirectory());        }    }    private static class SimpleProgressMonitor implements ProgressMonitor {        @Override        public void start(int totalTasks) {            System.out.println("Starting work on " + totalTasks + " tasks");        }        @Override        public void beginTask(String title, int totalWork) {            System.out.println("Start " + title + ": " + totalWork);        }        @Override        public void update(int completed) {            System.out.print(completed + "-");        }        @Override        public void endTask() {            System.out.println("Done");        }        @Override        public boolean isCancelled() {            return false;        }    }}

代码执行后曾经是 OK 了,能够间接拉取到代码,那么至此,差不多曾经 OK 了,前面把代码间接丢服务器下来跑着就拉倒了。

服务器的问题

好了,你认为到这里就完结了吗?年老了,年老了。。。

这代码丢到服务器上跑会发现报错连不上 github,如果你是阿里云服务器的话。

解决方案是找到 /etc/ssh/ssh_config,删掉 GSSAPIAuthentication no 这行后面的正文,而后保留,你才会发现真的是能下载了。

同时,nginx 咱们映射另外一个域名作为回调的域名,这里须要次要以下你的 https 证书,因为我是免费版的,所以 https 这个域名无奈失效,那 webhook 回调留神用 http 就行。

server {    listen 80;    listen [::]:80;    server_name test.aixiaoxian.vip;    client_max_body_size 1024m;    location / {        proxy_pass http://127.0.0.1:9000;        proxy_set_header HOST $host;        proxy_set_header X-Forwarded-Proto $scheme;        proxy_set_header X-Real-IP $remote_addr;        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;    }}server {    listen       443 ssl;    server_name  test.aixiaoxian.vip;    root         /usr/share/nginx/html;    ssl_certificate cert/aixiaoxian.pem;    ssl_certificate_key cert/aixiaoxian.key;    ssl_session_cache shared:SSL:1m;    ssl_session_timeout  10m;    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;    ssl_prefer_server_ciphers on;    ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #示意应用的TLS协定的类型。    include /etc/nginx/default.d/*.conf;    location / {        proxy_pass http://127.0.0.1:9000;        proxy_set_header HOST $host;        proxy_set_header X-Forwarded-Proto $scheme;        proxy_set_header X-Real-IP $remote_addr;        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;    }}

OK,如果你用下面的形式还无奈解决,能够换一种形式,间接通过 Java 间接 shell 脚本去解决。

rm -rf /root/docs/JavaInterviewgit clone https://github.91chi.fun//https://github.com/irwinai/JavaInterview.git

用脚本的目标是能够用减速的 git 地址,否则服务器拜访常常会失败,这个找个插件就能够有减速地址了。

public class ShellCommandsHelper extends CommandHelper {    private static final File file = new File(FILE_PATH);    @Override    public void exec() {        try {            FileUtils.forceMkdir(file);            log.info("Starting clone repository...");            Process process = Runtime.getRuntime().exec("sh " + SHELL_PATH, null, file);            int status = process.waitFor();            if (status != 0) {                log.error("[ShellCommandsHelper] exec shell error {}", status);            }        } catch (Exception e) {            log.error("[ShellCommandsHelper] exec shell error", e);        }    }}

代码上传的问题

好了,这样通过手动上传代码的形式其实曾经能够用了,然而为了部署更不便一点,我倡议装置一个插件Alibaba Cloud Toolkit,其余云服务器有的也有这种类型的插件。

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

去本人的阿里云账号上面配置。

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

而后在这里会主动加载出你的服务器信息,而后抉择本人的部署目录,同时抉择一个 Command 也就是执行命令。

这个命令轻易找个目录创立一个这个脚本,放那里就行了,最初点击 Run,代码就飞快的上传了。

source /etc/profilekillall -9 javanohup java -jar /root/tools/sync-tools-0.0.1-SNAPSHOT.jar > nohup.log 2>&1 &

完结

根本的工作曾经做完了,那其实还有挺多细节问题没有解决的,比方失败重试、回调鉴权等等问题,这只是一个十分高级的版本。

同步代码 git 地址:https://github.com/irwinai/sy...

文章仓库地址:https://github.com/irwinai/Ja...

博客地址:https://aixiaoxian.vip/#/