NodeJs 实现 github 图床向阿里云 Oss 的自动搬运
最近越来越觉得 github 慢,各种 pull,push 卡顿,家里打开 github 网站也巨慢,博客文章打开,各种图裂;现在不止用 vscode 写代码,还用来写文章,写笔记。所以努力给自己打造一个舒适的写作工具,非常重要。以前都用 segmentfault, github 来做自己的图片云,可惜免费的始终是有代价的。一直用 github issue 写文章的我,终于忍不住了,我要换图床。机智的我,去年有活动时,不止买了个 3 年 229 的服务器,还话 45 元买了一个 Oss 仓库。
vscode 本地图床插件:Picgo
以前都是去 github 上传了文件,再把地址拷到 vscode 的文件里,最近想自己写个插件,实现 vscode 直接上传图片到 Oss。一百度,发现自己确实挺落伍,发现个插件 Picgo , 我用的阿里云 Oss,贴上我个人的 vscode 配置:
标红的选项很重要。配置完后,截个屏,cmd + option + u 就可以马上感受一下在 vscode 插图的快感了。
历史文章的图片处理
无法忍受 github 图片随时图裂,打开缓慢。既然已经有了 Oss,那就全部替换了吧,但那么多文章(40+)和笔记(60+),一篇一篇 copy,那 cmd 键估计都按白了吧。懒惰的我,肯定不会,肯定不会用这么土的办法。所以机智的我,基于 NodeJs 写了一个小工具。
技术栈(Node):fs + http + Oss-Sdk
原理:
- 遍历文章源文件,读取图片地址, 并做标记;
- 下载图片,并上传到 Oss,获取新的图片地址;
- 用新的图片地址更新标记位置;
- 更新文件;
代码(涉及到较多正则匹配和骚操作,看不懂的请忽略):
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 在国内被墙耽误了不少时间,但我的情况你并不一定遇到,比如:
但机智的我,把 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; } } }
延伸阅读:
暂无内容!
评论列表 (0条):
加载更多评论 Loading...