使用github pages作为图片静态服务器

在一些包含服务器的个人项目上,图片等静态资源一般有两种处理方式:直接使用业务服务器处理和存储,或者交给专门的图片服务器去处理和存储。这两种方式都需要服务相对稳定和长效,不然的话总要面对数据的迁移和维护,这是一项成本,不小心的时候还会有坑存在。然而有些时候,比如我们的个人博客,或者用于演示的一些demo中的图片,一定需要图片服务器吗?对于这种需求量级并不大的静态资源存储,我们是否可以直接用自己的github作为服务器,如果可以,对个人项目来说这个静态资源服务器将会是非常稳定的,除非存储量达到了你的github空间上限或者因为某些原因github不再能被人们访问。

分析:

如何使用github作为静态资源服务器呢,很简单,只要实现以下三点就可以:
1. 捕获客户端的上传资源请求并处理
2. 关联github.io本地仓库,将上一步的处理结果同步到这个本地仓库
3. 远程推送,返回客户端可访问的静态资源链接

除了以上三点,还需要一个配置项来关联。这里实现一个基于koa的使用github.io作为图片静态服务器的中间件,按照上面的分析思路,实现如下:

配置文件

下面是期望的用法,通过将配置项传入githubAsImageServer得到中间件,并挂载到koa实例上来实现上述功能。

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

很明显,githubAsImageServer这个函数是实现一切的逻辑所在,代码结构如下:

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格式。

完整代码参见github.com/silentport/…,如果你感兴趣,欢迎与我讨论或者提issue。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值