Node實現github圖牀向阿里雲Oss的自動搬運

原油

最近原油下跌很多,疫情不斷,股市惶恐不安。於我而言,管我屁事,沒錢。奮(pin)鬥(qiong)的我,只有好好寫代碼。偏題了,是想說原因來着,最近愈來愈以爲github慢,各類pull,push卡頓,家裏打開github網站也巨慢,博客文章打開,各類圖裂;如今不止用vscode寫代碼,還用來寫文章,寫筆記。因此努力給本身打造一個溫馨的寫做工具,很是重要。之前都用segmentfault, github來作本身的圖片雲,惋惜免費的始終是有代價的。一直用github issue寫文章的我,終於忍不住了,我要換圖牀。機智的我,去年有活動時,不止買了個3年229的服務器,還話45元買了一個Oss倉庫。
20200318215710.pnggit

vscode 本地圖牀插件:Picgo

之前都是去github上傳了文件,再把地址拷到vscode的文件裏,最近想本身寫個插件,實現vscode直接上傳圖片到Oss。一百度,發現本身確實挺落伍,發現個插件Picgo, 我用的阿里雲Oss,貼上我我的的vscode配置:
20200318221143.pnggithub

標紅的選項很重要。配置完後,截個屏,cmd + option + u 就能夠立刻感覺一下在vscode插圖的快感了。segmentfault

歷史文章的圖片處理

沒法忍受github圖片隨時圖裂,打開緩慢。既然已經有了Oss,那就所有替換了吧,但那麼多文章(40+)和筆記(60+),一篇一篇copy,那cmd鍵估計都按白了吧。懶惰的我,確定不會,確定不會用這麼土的辦法。因此機智的我,基於NodeJs寫了一個小工具。數組

技術棧(Node):fs + http + Oss-Sdk 瀏覽器

原理:服務器

  1. 遍歷文章源文件,讀取圖片地址, 並作標記;
  2. 下載圖片,並上傳到Oss,獲取新的圖片地址;
  3. 用新的圖片地址更新標記位置;
  4. 更新文件;

代碼(涉及到較多正則匹配和騷操做,看不懂的請忽略):app

const path = require('path');
const fs = require('fs');
const util = require('util');
const https = require("https");
const http = require("http");
const stream = require('stream');
const Oss = require('./oss');

// NOTE: 方案測試經過;
const isExists = util.promisify(fs.exists);
const readFile = util.promisify(fs.readFile); 
const writeFile = util.promisify(fs.writeFile); 

const reg = /\!\[[\s\S]{3,20}\]\((http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?\)/g;

const urlReg = /(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?/;

const typeReg = /http.*(?=\.[jpg,png,jpeg,gif])/;
const HasReg = /http.*\.(jpg|png|jpeg|gif)$/;

const httpReg = /^http:.+/;
const BUcket_Dir = 'article';

function getRadomName() {
  const randomCode = Math.floor(Math.random() * 26) + 65;
  return `${Date.now()}-${String.fromCharCode(randomCode)}`;
}

const config = {
  accessKeyId: 'your id',
  accessKeySecret: 'your secret'
};

const oss = new Oss()

function httpGet(url, enable, check) {
  return new Promise((resolve,reject) => {
    const method = httpReg.test(url) ? http : https;
    method.get(url,
      (res) => {
      const chunks = [];
      let size = 0;
      enable && console.log('start');

      if (check) {
        if (res.statusCode != 200) {
          resolve('none');
        } else {
          resolve(url);
        }
        return;
      }

      if (res.statusCode == 301) {
        resolve('move');
      }
      res.on('data', (data) => {
        // 收集獲取到的文件流
        // console.log('trans', data.length);
        chunks.push(data);
        size += data.length;
      });
      res.on('end', () => {
        // 文件流拼接獲取buffer
        enable && console.log('end', size);
        resolve(Buffer.concat(chunks, size));
      });
    })
  }).catch((error) => {
    console.error(error);
  });
}
async function getImage({ url, dir }) {
  // 判斷url 是否帶有圖片類型標識, 而後生成新的圖片地址
  let target;
  if (url.indexOf('user-images.githubusercontent.com') > 1) {
    url = url.replace('https://user-images.githubusercontent.com', 'http://github-production-user-asset-6210df.s3.amazonaws.com');
    const arr = url.split('/');
    target = `${dir}${arr[arr.length-1]}`;
    const ossUrl = `https://doddle.oss-cn-beijing.aliyuncs.com/${target}`;
    // 檢查文件是否已上傳過
    const checkExsit = await httpGet(ossUrl, true, true);
    if (checkExsit === ossUrl) {
      return ossUrl;
    }
  } else {
    target = HasReg.test(url) ?
    url.replace(typeReg, `${dir}${getRadomName()}`) :
    `${dir}${getRadomName()}.png`;
  }
  // 獲取buffer
  const buffer = await httpGet(url, true);
  // 發生錯誤,原地址直接返回,不作替換;
  if (!buffer) {
    console.log('error happen');
    return url;
  }
  // 若是響應301,則原地址直接返回
  if (buffer === 'move') {
    return url;
  }
  // 生成臨時文件流, 並將Buffer寫入流中
  const fileSream = new stream.PassThrough();
  fileSream.end(buffer);

  // 上傳,並獲取存儲的url
  const result = await oss.uploadStream(target, fileSream);
  return result.url;
}

async function replaceUrl(file, name) {
  const dir = `${BUcket_Dir}/${name ? name : file.replace('.md', '')}/`;
  const filePath = path.resolve(__dirname, '../arcticle/', file);

  const isExist = await isExists(filePath);

  // oss 只初始化一次;
  if (!oss.init) {
    oss.create(config);
  }

  // 判斷目標文件是否存在;
  if(!isExist) {
    console.log('file:', file, 'is not exist');
    return;
  }

  // 讀取文件內容
  const content = await readFile(filePath,'utf8');

  let count = 0;
  const queue = [];
  // 內容替換;
  const con = content.replace(reg, (str) => {
    // oss 北京的,就不用替換了
    if (str.indexOf('doddle.oss-cn-beijing') > 0) {
      return str;
    }
    const url = str.slice(str.indexOf('(') + 1, str.length - 1);
    // 佔位符
    const address = `url-${count}-end`;
    queue.push({ url, dir });
    // console.log('new', newUrl);
    count++;
    // 先用站位符替換目標Url,後面再進行下一步;
    return str.replace(urlReg, address);
  });

  // 根據獲取原始url,獲取對應的Oss url
  const res = await Promise.all(queue.map((param) => getImage(param)));
  // console.log('count', count, res);

  count = 0;
  // 根據響應的url數組,替換站位符號
  const final = con.replace(/url-[\d]+-end/g, function() {
    return res[count++];
  });

  // 反寫文件
  await writeFile(filePath, final, {
    encoding: 'utf-8'
  });
  console.log('finish the file:', file);
}

fs.readdir('./arcticle', (err, files) => {
  // console.log('file', files.length);
  files.forEach((file) => {
    // console.log('file', file);
    replaceUrl(file, 'shim');
  });
})

原理其實很簡單,實現也沒花多長時間,但仍是由於github在國內被牆耽誤了很多時間,但個人狀況你並不必定遇到,好比:
20200318222626.pngdom

但機智的我,把https連接換成http,發現圖片也能訪問,但用Node 發起http請求時,發現響應301,再一看瀏覽器,發現確實被重定向了。因此就有了下面這段代碼:async

if (url.indexOf('user-images.githubusercontent.com') > 1) {
    url = url.replace('https://user-images.githubusercontent.com', 'http://github-production-user-asset-6210df.s3.amazonaws.com');
}

上面代碼註釋給的很詳細了,看不懂能夠留言。工具

附Oss封裝代碼:

const OSS = require('ali-oss');

module.exports =  class MyOss {
  constructor() {
    this.client = null;
    this.upload = this.upload.bind(this);
    this.uploadStream = this.uploadStream.bind(this);
    this.getList = this.getList.bind(this);
    this.init = false;
  }

  // 初始化Client
  create({ accessKeyId, accessKeySecret }) {
    this.client = new OSS({
      bucket: 'doddle',
      region: 'oss-cn-beijing',
      accessKeyId,
      accessKeySecret,
      secure: true, // 設置爲true,返回的url纔是https的
    });
    this.init = true;
  }

  async upload({ file, key, options = {} }) {
    try {
      const result = await this.client.put(key, file, options);
      return result;
    } catch (e) {
      console.log(e);
      return false;
    }
  }

  async uploadStream(name, file) {
    try {
      const result = await this.client.putStream(name, file);
      return result;
    } catch (e) {
      console.log(e);
      return false;
    }
  }

  async getList(dir = '') {
    try {
      const result = await this.client.list({
        prefix: dir
      });
      return result;
    } catch (e) {
      console.error(e);
      return false;
    }
  }
}
相關文章
相關標籤/搜索