Github图床使用

图床就是公网上存放图片的地方,常用的有七牛,又拍云,新浪微博等,前面两个图床是免费的,但是需要绑定到自己已备案的域名,自己没有域名,就算了,新浪微博直接用就行了,但是据说有可能会被删除。用github的图床就没有上面这两个问题,而且查看和管理起来也很方便,所以就在想怎么实现。一顿搜索之后在网上找到了一个APP,PicGo,它里面有一个上传图片到GitHub图床的功能。

PicGo

PicGo在使用GitHub图床之前要先配置,仓库名一定要加上你的username,我刚开始填的是repository的名字,就一直上传不成功,分支名一般就用master,Token需要到设置中添加一个,路径是这个,点击Generate a new token,填写一个Note,把第一组Scope勾上,生成就可以了,注意这个token只能在生成的时候可以查看,关掉页面就看不到了,需要重新生成了。

New token

GitHub设置

把token填进去,点击确定,去测试一下,没有问题。

这是一个开源的APP,可以去项目中找找GitHub图床上传的代码,于是直接把代码clone下来。项目是用electron-vue写的,之前虽然没写过,但是HTML/JS还是看得懂一点的。就一点一点地扒代码。

首先全文搜索了一下github,一通找了之后发现GitHub.vue这个页面的布局跟上面的设置页面很像。找到了一个确定和设为默认图床两个按钮,确定按钮调用了confirm方法,首先是form校验,通过后把form数据保存起来。

    confirm () {
      this.$refs.github.validate((valid) => {
        if (valid) {
          this.$db.set('picBed.github', this.form).write()
          const successNotification = new window.Notification('设置结果', {
            body: '设置成功'
          })
          successNotification.onclick = () => {
            return true
          }
        } else {
          return false
        }
      })
    }

设为默认图床,这个按钮点击后调用setDefaultPicBed这个方法

    // ConfirmButtonMixin.js
    setDefaultPicBed (type) {
      this.$db.read().set('picBed.current', type).write()
      this.defaultPicBed = type
      const successNotification = new window.Notification('设置默认图床', {
        body: '设置成功'
      })
      successNotification.onclick = () => {
        return true
      }
    }

到这里GitHub设置相关的已经完成了,那我要找一下上面的设置在哪里有用到。我就又全文搜索了一下上面的keypicBed.current,在一个Upload.vue文件中找到了获取了这个key对应的值,经过比对,发现这就是上面第一个节目对接的文件。

然后分析里面的布局,有一个引起了我的注意:

<div id="upload-dragger" @click="openUplodWindow">
  <i class="el-icon-upload"></i>
  <div class="upload-dragger__text">
    将文件拖到此处,或 <span>点击上传</span>
  </div>
  <input type="file" id="file-uploader" @change="onChange" multiple>
</div>

这是中心区域的布局,可以看到有一个file-uploader,触发的事件是onChange方法:

    onChange (e) {
      this.ipcSendFiles(e.target.files)
      document.getElementById('file-uploader').value = ''
    },
    ipcSendFiles (files) {
      let sendFiles = []
      Array.from(files).forEach((item, index) => {
        let obj = {
          name: item.name,
          path: item.path
        }
        sendFiles.push(obj)
      })
      this.$electron.ipcRenderer.send('uploadChoosedFiles', sendFiles)
    },

说实话到这里就开始闷逼了,ipcSendFiles方法里最后一句是什么意思,完全不明白。然后就开始猜测,uploadChoosedFiles是不是一个方法,sendFiles是参数,这个就有点像OC语法中的objc_msgSend,全文搜索uploadChoosedFiles,果然在index.js中找到了这个方法的定义:

const uploadChoosedFiles = async (webContents, files) => {
  const input = files.map(item => item.path)
  const imgs = await new Uploader(input, webContents).upload()
  // 省略后面代码...
}

这里最关键的就是构建了一个Uploader对象,并调用upload方法,里面创建了一个PicGo对象,调用upload方法,再追踪这个PicGo类就找不到了,只找到下面这个import:

const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require
const PicGo = requireFunc('picgo')

刚开始真的不知道该怎么继续了,因为对JS语法的理解也就只是在写写业务的层面,基本没有接触过webpack,去接了杯水,回来后想这是不是就是一个普通的依赖呢,代码里没有一个picgo.js的文件,就到package.json中找(这是一个前端项目管理依赖的文件,类似已iOS中的Podfile),果然在dependencies中有一个"picgo": "^1.3.7"。带NPM官网搜索picgo这个包,找到了下面这个

picgo @ npmjs.com

右边有这个包的官网和github代码仓库,于是跳转过去,又把代码clone下来。拿到代码后在src目录下面翻找,看到有一个PicGo.ts的文件(TypeScript),应该就是这个了。

里面有一个upload方法

async upload (input?: any[]): Promise<void | string | Error> {
    if (this.configPath === '') return this.log.error('The configuration file only supports JSON format.')
    // upload from clipboard
    if (input === undefined || input.length === 0) {
      try {
        const { imgPath, isExistFile } = await getClipboardImage(this)
        if (imgPath === 'no image') {
          throw new Error('image not found in clipboard')
        } else {
          this.once('failed', async () => {
            if (!isExistFile) {
              await fs.remove(imgPath)
            }
          })
          this.once('finished', async () => {
            if (!isExistFile) {
              await fs.remove(imgPath)
            }
          })
          await this.lifecycle.start([imgPath])
        }
      } catch (e) {
        this.log.error(e)
        this.emit('failed', e)
        throw e
      }
    } else {
      // upload from path
      await this.lifecycle.start(input)
    }
  }

看到最终调用的是lifecycle.start()方法,继续追进去,lifecycle.start() -> lifecycle.doUpload()

  private async doUpload (ctx: PicGo): Promise<PicGo> {
    this.ctx.log.info('Uploading...')
    let type = ctx.config.picBed.uploader || ctx.config.picBed.current || 'smms'
    let uploader = this.ctx.helper.uploader.get(type)
    if (!uploader) {
      type = 'smms'
      uploader = this.ctx.helper.uploader.get('smms')
      ctx.log.warn(`Can't find uploader - ${type}, swtich to default uploader - smms`)
    }
    await uploader.handle(ctx)
    for (let i in ctx.output) {
      ctx.output[i].type = type
    }
    return ctx
  }

里面有个根据type获取uploader的方法,这个type使用的是picBed.current,这个是不是很熟悉,就是上面设置默认图床设置的key。分析获取uploader对象的方法,ctx是PicGo对象,helper以及uploader是在PicGo对象的构造方法中初始化的,构造函数中uploader还是一个空的plugin,在init方法中调用了uploaders方法,这个方法正真给uploader对象添加了多个不同类型的Uploader具体实现。这也是策略模式的一种实践。

export default (ctx: PicGo): void => {
  ctx.helper.uploader.register('smms', SMMSUploader)
  ctx.helper.uploader.register('tcyun', tcYunUploader)
  ctx.helper.uploader.register('weibo', weiboUploader)
  ctx.helper.uploader.register('github', githubUploader)
  ctx.helper.uploader.register('qiniu', qiniuUploader)
  ctx.helper.uploader.register('imgur', imgurUploader)
  ctx.helper.uploader.register('aliyun', aliYunUploader)
  ctx.helper.uploader.register('upyun', upYunUploader)
}

到这里,我们已经找到了github图床上传的具体实现类,除此之外,还有很多其他类型的,比例微博,七牛,又拍云等,这次我们只关注github的实现。进入github.ts这个文件,查看handle方法。

const postOptions = (fileName: string, options: any, data: any): any => {
  const path = options.path || ''
  const { token, repo } = options
  return {
    method: 'PUT',
    url: `https://api.github.com/repos/${repo}/contents/${encodeURI(path)}${encodeURI(fileName)}`,
    headers: {
      Authorization: `token ${token}`,
      'User-Agent': 'PicGo'
    },
    body: data,
    json: true
  }
}

// handle方法片段
let base64Image = imgList[i].base64Image || Buffer.from(imgList[i].buffer).toString('base64')
const data = {
  message: 'Upload by PicGo',
  branch: githubOptions.branch,
  content: base64Image,
  path: githubOptions.path + encodeURI(imgList[i].fileName)
}
const postConfig = postOptions(imgList[i].fileName, githubOptions, data)
const body = await ctx.Request.request(postConfig)

对图片进行base64,转成字符串,构建请求头和请求参数,PUT方法发送请求。

到这里已经很兴奋了,这其实就是一个Http请求就可以把图片放到github上去了,赶紧用第三方的HTTP客户端试一下自己拼一个请求。我这里使用Paw这个软件。

Header
Body

这里User-Agent使用了默认的,文件名使用了Paw中的Timestamp方法,content使用文件的base64,成功了,到github仓库中也看到了上传的这张图片。好厉害。

开发者网站

后来去github的developer页面,才发现自己走了一大段弯路,在Github开发者网站有专门一个章节介绍怎么组装一个创建文件的请求,以后想要实现一个功能,要先找一下官方有没有相关的文档。

GitHub开发者网站

其实,不光是图片,任何的文件都可以通过这种方式上传到github上。

这篇文章也是记录一下自己通过这个开源项目寻找关键代码的过程,可能会有点啰嗦,但是这个过程对我来说很奇妙。通过这个工程也可以看其他几个图床的上传方式。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容

  • 项目地址:https://github.com/Molunerfinn/PicGo 本人经常使用Markdown来...
    GitHub_itgoyo阅读 15,717评论 2 8
  • 对于许多像笔者一样使用 Markdown 写作的人来说,在文章中插入图片是一件非常普遍的事。而就是这样一件普遍的事...
    神齐阅读 1,778评论 2 49
  • 前言 用过几款上传图片到图床的软件,但是自己常用的图床,比如青云对象存储基本都没有支持的。 刚好前几天发现了一款可...
    chengww阅读 3,212评论 4 0
  • 高考,一个熟悉又陌生的名词。说它熟悉是因为从记事起就听大人们在谈论高考,说不熟悉是因为我从来没有接触过它。...
    雪樱2阅读 203评论 0 0
  • 日光浅浅,夕阳渐沉 旅途慢慢被拉长 疲倦的知了也悄悄收了声 睡意沉沉 窗外的青枝微弯 是有风吹过 带走几丝蒸腾的热...
    木粥粥阅读 889评论 0 4