专栏首页玩转全栈MarkDown写作上传图床神器picGo原理
原创

MarkDown写作上传图床神器picGo原理

背景

最近朋友推荐了一个用markdown写博客的神器,typora,用了一下,真的是好用到哭,之前是使用的markText,其实也挺好用的,这两个都是开源的,相对来说,Marktext做的更加美观点,Marktext唯一的缺点是,我在使用的时候,粘贴图片时上传是有bug的。因此,决定用下typora。切换到typora之后,发现快捷建基本上和Marktext是一样的,真的是无痛切入,然后剩下的就是配置一下如何上传图片了。

配置图片上传

配置图片地方可以通过快捷键cmd+,呼出。上传服务有多张方式,我选择Custom Command,执行的命名是PicGo.

首先我们来探讨一下,为何使用PicGo。其实,typora是有非常多种选择的:

对比了一下iPicuPic,他俩都是收费的,因此在有免费的情况下优先不考虑,然后picGo是有GUI版本和命令行(CLI)版本的,这里我之所以考虑CLI版本的,是因为,实际上,上传图片只有在我写markdown的时候需要,因此,装一个GUI版本的显得有些重,在者,安装包也有点大,Mac空间还是有限的,本着能省就省的原则,果断选择安装CLI版本。

这里注意一下,在配置命令的时候:

1、如果说找不到node,那么你需要认为指定node路径。因为通过源码可以发现,picgo默认的node路径是:#!/usr/bin/env node

<img src="https://raw.githubusercontent.com/bravekingzhang/pic_go/master/2020/0E505D78-BE29-41A6-8459-A5A2D99C0DAD.png" alt="0E505D78-BE29-41A6-8459-A5A2D99C0DAD" style="zoom: 50%;" />

2、如果你公司的网络有代理,那么请使用 --proxy|-p 添加代理,比如: -p http://127.0.0.1:12379

PicGo实现原理

如图所示,复制一张图片,在命令行下执行上述命令,图片就上传了,我这里配置的是github作为图床。用了之后,真心感慨,PicGo是在是好用,对于我现在写markdown的需求来说,当真是无可挑剔了。所以,想着,是否应该研究下他的原理呢?

picgo-core

通过这个流程图,我们可以发现,picgo V2版本确实比较清晰,让我想起了移动端那个图片加载神器Glide,可以提供各种各样的Transformer,来做图片处理,比如圆角,加边框,缩放,加水印等等。尽然没想到,一个小小的PicGo居然也做得这么人性化。

先看看,picgo的主流程,如何实现图片上传的。

首先,我们知道,PicGo上传有两种方式:

1、直接上传剪切板复制的图片。

2、上传指定路径下的图片。

其实,两种方式都是一样的,通过源码可以看到,通过剪切板方式传图片,实际上也是最终转化为图片路径的方式。

所以,我们的主流程还是没变,那就是 upload imagePath.其实就是上面那个 lifecycle.start(input)了,所以,跟踪一下:

async start (input: any[]): Promise<PicGo> {
    try {
      // images input
      if (!Array.isArray(input)) {
        throw new Error('Input must be an array.')
      }
      this.ctx.input = input
      this.ctx.output = []

      // lifecycle main
      await this.beforeTransform()
      await this.doTransform()
      await this.beforeUpload()
      await this.doUpload()
      await this.afterUpload()
      return this.ctx
    } catch (e) {
      this.ctx.log.warn('failed')
      this.ctx.emit('uploadProgress', -1)
      this.ctx.emit('failed', e)
      this.ctx.log.error(e)
      if (this.ctx.getConfig<Undefinable<string>>('debug')) {
        throw e
      }
      return this.ctx
    }
  }

可以看到,这里就是我们原理环节提到的那个流程了,doTransform->doUpload等环节了。我们先看看Transform做了些什么?

private async doTransform (): Promise<PicGo> {
    this.ctx.emit('uploadProgress', 30)
    this.ctx.log.info('Transforming...')
    const type = this.ctx.getConfig<Undefinable<string>>('picBed.transformer') || 'path'
    let transformer = this.ctx.helper.transformer.get(type)
    if (!transformer) {
      transformer = this.ctx.helper.transformer.get('path')
      this.ctx.log.warn(`Can't find transformer - ${type}, switch to default transformer - path`)
    }
    await transformer?.handle(this.ctx)
    return this.ctx
  }

默认就是找到path这个来处理,而这个path,我们看看,他在plugins/transformer/包下,看下实际上做了啥?

const handle = async (ctx: PicGo): Promise<PicGo> => {
  const results: IImgInfo[] = ctx.output
  await Promise.all(ctx.input.map(async (item: string, index: number) => {
    let info: IPathTransformedImgInfo
    if (isUrl(item)) {
      info = await getURLFile(item)
    } else {
      info = await getFSFile(item)
    }
    if (info.success && info.buffer) {
      try {
        const imgSize = getImgSize(ctx, info.buffer, item)
        results[index] = {
          buffer: info.buffer,
          fileName: info.fileName,
          width: imgSize.width,
          height: imgSize.height,
          extname: info.extname
        }
      } catch (e) {
        ctx.log.error(e)
      }
    } else {
      ctx.log.error(info.reason)
    }
  }))
  // remove empty item
  ctx.output = results.filter(item => item)
  return ctx
}

其实就是处理下拿到 IPathTransformedImgInfo 这个信息,这个信息其实是有冗余的,因为PicGo实现的是多图床上传,因此,不同的图片所需的参数必然不同,需要一个可以包容较多场景的描述信息,而这个就是IPathTransformedImgInfo

export interface IImgInfo {
  buffer?: Buffer
  base64Image?: string
  fileName?: string
  width?: number
  height?: number
  extname?: string
  [propName: string]: any
}

export interface IPathTransformedImgInfo extends IImgInfo {
  success: boolean
}

接下来,我们直接进入upload环节,其代码放在/plugin/upload中,这里面,作者已经预制了很多个图床上传插件了,因为我使用的github图床,因此就看了下github:

const handle = async (ctx: PicGo): Promise<PicGo> => {
  const githubOptions = ctx.getConfig<IGithubConfig>('picBed.github')
  if (!githubOptions) {
    throw new Error('Can\'t find github config')
  }
  try {
    const imgList = ctx.output
    for (const img of imgList) {
      if (img.fileName && img.buffer) {
        const base64Image = img.base64Image || Buffer.from(img.buffer).toString('base64')
        const data = {
          message: 'Upload by PicGo',
          branch: githubOptions.branch,
          content: base64Image,
          path: githubOptions.path + encodeURI(img.fileName)
        }
        const postConfig = postOptions(img.fileName, githubOptions, data)
        const body = await ctx.Request.request(postConfig)
        if (body) {
          delete img.base64Image
          delete img.buffer
          if (githubOptions.customUrl) {
            img.imgUrl = `${githubOptions.customUrl}/${githubOptions.path}${img.fileName}`
          } else {
            img.imgUrl = body.content.download_url
          }
        } else {
          throw new Error('Server error, please try again')
        }
      }
    }
    return ctx
  } catch (err) {
    ctx.emit('notification', {
      title: '上传失败',
      body: '服务端出错,请重试'
    })
    throw err
  }
}

通过这个重配置文件中读取我们对于github图床的配置,比如分支,token等信息,对,就是这行代码。

const githubOptions = ctx.getConfig<IGithubConfig>('picBed.github')

其他部分就是很简单了,对接github的文件上传api,我们关心拿到结果之后,拿到结果之后,如果用户配置了customUrl,那么这里就可以把图片链接包装为CDN链接,因为考虑到有些地方访问github图片还是挺慢的,这个是在是太人性化了。

总结

单纯的实现文件复制到剪切板,然后调用github的接口上传到github上,这个过程其实不难,关键是如何做的这么通用,足见作者是花了心思的。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • koa源码解析,理解洋葱模型

    之前,我一直在使用express做简单的后台server,写一些api,给自己做的前端来提供服务,觉着吧挺好用的,虽然koa也出来挺久的,但是我一直没有更换过,...

    brzhang
  • flutter单引擎方案

    假设有两个模块,FlutterA,FlutterB,我们利用io.flutter.embedding.android.FlutterFragment下面的接入方...

    brzhang
  • flutter启动流程跟踪简析

    我们项目接入flutter由来已久,采用的是混栈开发的方式,没有办法,因为项目开发了比较久,全部替换为flutter实现想想也不太现实,混栈开发的过程中,我们遇...

    brzhang
  • JavaScript中this引发的bug

    在JavaScript中有一个很特别、很常用又常常让初学者很困扰的东西 ─ “this”,在这堂课中会来谈谈这个”this”。

    Fundebug
  • 情人节送给粉丝的礼物

    代码不算多,特效也不是很花哨,情人节她更在意的是你口中的甜言蜜语,这个动画是你们交流的开端。 祝愿你们心心念念的人也在念着你们,愿你们web学习一帆风顺,无往...

    我不是费圆
  • 如何在Vuejs中实现页面空闲超时检测

    您是否需要检查用户在Vue应用程序中的不活跃状态?如果用户在一段时间内处于非活动状态,则要自动注销该用户或显示一个计时器。通常,具有机密数据的系统(如银行)通常...

    前端知否
  • canvas绘制图形时beginPath应用

    当我们在使用canvas绘制图形的时候,不免都会使用到beginPath这个方法。今天我们就来讲解一下这个函数的重要性以及应用。

    小明爱学习
  • 【不可思议的CANVAS】画一只会跟着鼠标走的小狗

    这个例子来自于[CodePen],它是根据鼠标的位置设置两个眼球的transform: rotate属性做的效果。

    coder_koala
  • Laravel服务提供器

    服务提供器是所有 Laravel 应用程序引导中心。你的应用程序自定义的服务、第三方资源包提供的服务以及 Laravel 的所有核心服务都是通过服务提供器进行注...

    KevinYan
  • 移动端APP列表点透事件处理方法

    关于点透事件这里不再赘述,如果不清楚的可以上网搜一搜,或者看小火柴的这篇文章。 这里是自己在做移动端时,在列表滑动的时候,遇到的点透问题。出现这个问题的来由是因...

    糊糊糊糊糊了

扫码关注云+社区

领取腾讯云代金券