1. 90前端首页
  2. 前端开发
  3. JavaScript

Node实现github图床向阿里云Oss的自动搬运

原油

最近原油下跌不少,疫情不断,股市惊慌失措。于我而言,管我屁事,没钱。奋(pin)斗(qiong)的我,只有好好写代码。偏题了,是想说缘由来着,最近越来越觉得github慢,各种pull,push卡顿,家里打开github网站也巨慢,博客文章打开,各种图裂;现在不止用vscode写代码,还用来写文章,写笔记。所以努力给自己打造一个舒适的写作工具,非常重要。以前都用segmentfault, github来做自己的图片云,可惜免费的始终是有代价的。一直用github issue写文章的我,终于忍不住了,我要换图床。机智的我,去年有活动时,不止买了个3年229的服务器,还话45元买了一个Oss仓库。
Node实现github图床向阿里云Oss的自动搬运

vscode 本地图床插件:Picgo

以前都是去github上传了文件,再把地址拷到vscode的文件里,最近想自己写个插件,实现vscode直接上传图片到Oss。一百度,发现自己确实挺落伍,发现个插件Picgo, 我用的阿里云Oss,贴上我个人的vscode配置:
Node实现github图床向阿里云Oss的自动搬运

标红的选项很重要。配置完后,截个屏,cmd + option + u 就可以马上感受一下在vscode插图的快感了。

历史文章的图片处理

无法忍受github图片随时图裂,打开缓慢。既然已经有了Oss,那就全部替换了吧,但那么多文章(40+)和笔记(60+),一篇一篇copy,那cmd键估计都按白了吧。懒惰的我,肯定不会,肯定不会用这么土的办法。所以机智的我,基于NodeJs写了一个小工具。

技术栈(Node):fs + http + Oss-Sdk

原理:

  1. 遍历文章源文件,读取图片地址, 并做标记;
  2. 下载图片,并上传到Oss,获取新的图片地址;
  3. 用新的图片地址更新标记位置;
  4. 更新文件;

代码(涉及到较多正则匹配和骚操作,看不懂的请忽略):

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在国内被墙耽误了不少时间,但我的情况你并不一定遇到,比如:
Node实现github图床向阿里云Oss的自动搬运

但机智的我,把https链接换成http,发现图片也能访问,但用Node 发起http请求时,发现响应301,再一看浏览器,发现确实被重定向了。所以就有了下面这段代码:

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;
    }
  }
}

本文来自网络整理,转载请注明原出处:https://segmentfault.com/a/1190000022064049

展开阅读全文

发表评论

登录后才能评论