從零開始爲 PicGo 開發一個新圖牀

前言

用過幾款上傳圖片到圖牀的軟件,可是本身經常使用的圖牀,好比青雲對象存儲基本都沒有支持的。html

恰好前幾天發現了一款能夠自定義插件的圖片上傳軟件 PicGo,藉此機會正好爲其新增青雲對象存儲圖牀的支持。java

picgo-qingstor-uploader-configuration.png

項目地址:picgo-plugin-qingstor-uploadernode

準備工做

插件基於 PicGo-Core 開發,參閱開發文檔 PicGo-Core-Doc 進行開發。git

  1. 確保已安裝 Node.js 版本 >= 8github

  2. 全局安裝算法

    yarn global add picgo # 或者 npm install picgo -g
    複製代碼
  3. 使用插件模板npm

    picgo init plugin <your-project-name>
    複製代碼
    • 全部插件以 picgo-plugin-xxx 的方式命名
    • 按照提示配置你的項目

開發插件

picgo 是個上傳的流程系統。所以插件其實就是針對這個流程系統的某個部件或者某些部件的開發。api

附一下流程圖:bash

picgo-core-fix.jpg

其中能夠供開發的部件總共有5個:async

兩個模塊:

  1. Transformer
  2. Uploader

三個生命週期插件入口:

  1. beforeTransformPlugins
  2. beforeUploadPlugins
  3. afterUploadPlugins

一般來講若是你只是要實現一個 picgo 默認不支持的圖牀的話,你只須要開發一個 Uploader

咱們這裏只是開發圖牀的話就只須要開發 Uploader 便可。


這裏定位到項目的 src/index.tssrc/index.js

在這裏就是你所要支持圖牀的配置的地方了。

圖牀配置文件

添加必須的配置項,新增圖牀配置:

import { PluginConfig } from 'picgo/dist/utils/interfaces'

const config = (ctx: picgo): PluginConfig[] => {
  let userConfig = ctx.getConfig('picBed.qingstor-uploader')
  if (!userConfig) {
    userConfig = {}
  }
  const config = [
    {
      name: 'accessKeyId',
      type: 'input',
      default: userConfig.accessKeyId || '',
      message: 'AccessKeyId 不能爲空',
      required: true
    },
    {
      name: 'accessKeySecret',
      type: 'password',
      default: userConfig.accessKeySecret || '',
      message: 'AccessKeySecret 不能爲空',
      required: true
    },
    {
      name: 'bucket',
      type: 'input',
      default: userConfig.bucket || '',
      message: 'Bucket不能爲空',
      required: true
    },
    {
      name: 'zone',
      type: 'input',
      alias: '區域',
      default: userConfig.area || '',
      message: '區域代碼不能爲空',
      required: true
    },
    {
      name: 'path',
      type: 'input',
      alias: '存儲路徑',
      message: 'blog',
      default: userConfig.path || '',
      required: false
    },
    {
      name: 'customUrl',
      type: 'input',
      alias: '私有云網址',
      message: 'https://qingstor.com',
      default: userConfig.customUrl || '',
      required: false
    }
  ]
  return config
}
複製代碼

簽名配置

根據青雲對象存儲簽名特色,使用 accessKeyId 和 accessKeySecret 生成上傳時的簽名。

  1. 首先觀察 strToSign :

    strToSign = Verb + "\n"
                  + Content-MD5 + "\n"
                  + Content-Type + "\n"
                  + Date + "\n"
                  (+ Canonicalized Headers + "\n")
                  + Canonicalized Resource
    複製代碼

    這裏只上傳圖片,Verb 就是 PUTDate 使用 new Date().toUTCString()

    考慮到簽名的複雜程度,上傳時不發送 Content-MD5 和 Content-Type 請求頭以下降簽名方法的複雜度。

  2. 而後就是 Canonicalized Headers :

    Canonicalized Headers 表明請求頭中以 x-qs- 開頭的字段。若是該值爲空,不保留空白行

    這種自定義的請求頭確定是沒有的,也能夠去掉。

  3. Canonicalized Resource 表明請求訪問的資源

    默認形式:/bucketName/path/fileName

    考慮到 pathfileName 可能的中文狀況,須要對其 encode 一下。

  4. strToSign 進行簽名

    將API密鑰的私鑰 (accessKeySecret) 做爲 key,使用 Hmac sha256 算法給簽名串生成簽名, 而後將簽名進行 Base64 編碼,最後拼接簽名。

完整代碼以下:

import crypto from 'crypto'

// generate QingStor signature
const generateSignature = (options: any, fileName: string): string => {
  const date = new Date().toUTCString()
  const strToSign = `PUT\n\n\n${date}\n/${options.bucket}/${encodeURI(options.path)}/${encodeURI(fileName)}`

  const signature = crypto.createHmac('sha256', options.accessKeySecret).update(strToSign).digest('base64')
  return `QS ${options.accessKeyId}:${signature}`
}
複製代碼

protocol 和 host

對於配置了 customUrl 的私有云用戶,須要獲取到 protocolhost

const getHost = (customUrl: any): any => {
  let protocol = 'https'
  let host = 'qingstor.com'
  if (customUrl) {
    if (customUrl.startsWith('http://')) {
      protocol = 'http'
      host = customUrl.substring(7)
    } else if (customUrl.startsWith('https://')) {
      host = customUrl.substring(8)
    } else {
      host = customUrl
    }
  }
  return {
    protocol: protocol,
    host: host
  }
}
複製代碼

配置 request

const postOptions = (options: any, fileName: string, signature: string, image: Buffer): any => {
  const url = getHost(options.customUrl)
  return {
    method: 'PUT',
    url: `${url.protocol}://${options.zone}.${url.host}/${options.bucket}/${encodeURI(options.path)}/${encodeURI(fileName)}`,
    headers: {
      Host: `${options.zone}.${url.host}`,
      Authorization: signature,
      Date: new Date().toUTCString()
    },
    body: image,
    resolveWithFullResponse: true
  }
}

複製代碼

配置插件 Plugin 的 handle

組合上述方法,處理上傳邏輯

const handle = async (ctx: picgo): Promise<picgo> => {
  const qingstorOptions = ctx.getConfig('picBed.qingstor-uploader')
  if (!qingstorOptions) {
    throw new Error('Can\'t find the qingstor config')
  }
  try {
    const imgList = ctx.output
    const customUrl = qingstorOptions.customUrl
    const path = qingstorOptions.path
    for (let i in imgList) {
      const signature = generateSignature(qingstorOptions, imgList[i].fileName)
      let image = imgList[i].buffer
      if (!image && imgList[i].base64Image) {
        image = Buffer.from(imgList[i].base64Image, 'base64')
      }
      const options = postOptions(qingstorOptions, imgList[i].fileName, signature, image)
      let body = await ctx.Request.request(options)
      if (body.statusCode === 200 || body.statusCode === 201) {
        delete imgList[i].base64Image
        delete imgList[i].buffer
        const url = getHost(customUrl)
        imgList[i]['imgUrl'] = `${url.protocol}://${qingstorOptions.zone}.${url.host}/${qingstorOptions.bucket}/${encodeURI(path)}/${imgList[i].fileName}`
      } else {
        throw new Error('Upload failed')
      }
    }
    return ctx
  } catch (err) {
    if (err.error === 'Upload failed') {
      ctx.emit('notification', {
        title: '上傳失敗!',
        body: `請檢查你的配置項是否正確`
      })
    } else {
      ctx.emit('notification', {
        title: '上傳失敗!',
        body: '請檢查你的配置項是否正確'
      })
    }
    throw err
  }
}
複製代碼

註冊插件

將 uploader 註冊便可:

export = (ctx: picgo) => {
  const register = () => {
    ctx.helper.uploader.register('qingstor-uploader', {
      handle,
      name: '青雲 QingStor',
      config: config
    })
  }
  return {
    uploader: 'qingstor-uploader',
    register
  }
}

複製代碼

發佈插件

  1. 先登陸 npm 帳號

    npm login
    複製代碼
  2. 發佈到 npm 上就能夠了

    npm publish
    複製代碼
相關文章
相關標籤/搜索