在一些包含服务器的我的项目上,图片等静态资源通常有两种处理方式:直接使用业务服务器处理和存储,或者交给专门的图片服务器去处理和存储。这两种方式都须要服务相对稳定和长效,否则的话总要面对数据的迁移和维护,这是一项成本,不当心的时候还会有坑存在。然而有些时候,好比咱们的我的博客,或者用于演示的一些demo中的图片,必定须要图片服务器吗?对于这种需求量级并不大的静态资源存储,咱们是否能够直接用本身的github做为服务器,若是能够,对我的项目来讲这个静态资源服务器将会是很是稳定的,除非存储量达到了你的github空间上限或者由于某些缘由github再也不能被人们访问。javascript
如何使用github做为静态资源服务器呢,很简单,只要实现如下三点就能够:
1. 捕获客户端的上传资源请求并处理
2. 关联github.io本地仓库,将上一步的处理结果同步到这个本地仓库
3. 远程推送,返回客户端可访问的静态资源连接
java
除了以上三点,还须要一个配置项来关联。这里实现一个基于koa
的使用github.io
做为图片静态服务器的中间件,按照上面的分析思路,实现以下:node
下面是指望的用法,经过将配置项传入githubAsImageServer
获得中间件,并挂载到koa
实例上来实现上述功能。git
const Koa = require('koa')
const app = new Koa()
const githubAsImageServer = require('github-as-image-server');
app.use(githubAsImageServer({
targetDir: 'D:/project/silentport.github.io', // github.io仓库的本地地址
repo: 'https://github.com/silentport/silentport.github.io.git', // github.io仓库的远程地址
url: 'https://silentport.github.io', // 你的github.io域名
dir: 'upload', // 图片文件的上传目录
project: 'blog', // 项目名,用于指定一个上传子目录
router: '/upload' // 请求路由,请求非该指定路由时中间件将跳过
}))
app.listen(8002, () => {
console.log('server is started!');
})
复制代码
很明显,githubAsImageServer
这个函数是实现一切的逻辑所在,代码结构以下:github
module.exports = options => async (ctx, next) => {
// 非上传请求直接跳过
if (ctx.method !== 'POST' || ctx.path !== options.router) {
next();
return;
}
let result = null; // 最终响应到客户端的值
const { targetDir, repo, url, dir, project } = options;
const uploadDir = targetDir + '/' + dir || 'upload'; // 上传目录
const childDir = uploadDir + '/' + project; // 上传子目录
const form = new formidable.IncomingForm();
// 在指定目录下执行shell命令
const execCommand = async (command, options = { cwd: targetDir }) => {
const ls = await exec(command, options);
console.log(ls.stdout);
console.log(ls.stderr);
};
const isExistDir = dir => fs.existsSync(dir);
// 确保文件夹存在
const ensureDirExist = dir => {
if (!isExistDir(dir)) fs.mkdirSync(dir);
}
// 远程推送
const pushToGithub = async imgList => {
await execCommand('git pull');
await execCommand('git add .');
await execCommand(`git commit -m "add ${imgList}"`);
await execCommand('git push');
}
// 解析上传请求后的回调
const callback = async (files, keys) => {
let result = { url: [] };
let imgList = [];
await (async () => {
for await (const key of keys) {
const originPath = files[key].path;
const targetPath = uniquePath(path.join(path.dirname(originPath), encodeURI(files[key].name)));
const imgName = targetPath.split(/\/|\\/).pop();
const webpName = imgName.split('.')[0] + '.webp';
const resUrl = url + '/upload/' + project + '/' + webpName;
const newPath = targetPath.replace(new RegExp(imgName), webpName);
fs.renameSync(originPath, targetPath);
try {
// 将图片转为webp格式,节省服务空间
await convertToWebp(targetPath, newPath);
} catch (err) {
next();
return;
}
imgList.push(webpName);
result.url.push(resUrl);
}
})();
await pushToGithub(imgList.toString());
return result;
}
// 文件名统一加上生成时间
const uniquePath = path => {
return path.replace(
/\.(png|jpe?g|gif|svg)(\?.*)?$/,
suffix => `_${getDate()}${suffix}`
);
}
// 本地github.io仓库不存在时先clone
if (!isExistDir(targetDir)) {
ensureDirExist(targetDir);
const cwd = targetDir.split('/');
cwd.pop();
await execCommand(`git clone ${repo}`, {
cwd: cwd.join('/')
});
}
ensureDirExist(uploadDir);
ensureDirExist(childDir)
form.uploadDir = childDir;
try {
result = await formHandler(form, ctx.req, callback, next);
} catch (err) {
result = {
url: []
}
}
ctx.body = result
};
复制代码
为了处理多图片上传请求的状况,待处理完毕后再统一返回客户端可访问的图片连接列表,这里用到了for await of
异步遍历,此语法只支持node 10.x
以上的版本。此外,为了节省服务空间,将全部图片转为了webp
格式。web
完整代码参见github.com/silentport/…,若是你感兴趣,欢迎与我讨论或者提issue。shell