用過幾款上傳圖片到圖牀的軟件,可是本身經常使用的圖牀,好比青雲對象存儲基本都沒有支持的。html
恰好前幾天發現了一款能夠自定義插件的圖片上傳軟件 PicGo,藉此機會正好爲其新增青雲對象存儲圖牀的支持。java
項目地址:picgo-plugin-qingstor-uploadernode
插件基於 PicGo-Core 開發,參閱開發文檔 PicGo-Core-Doc 進行開發。git
確保已安裝 Node.js 版本 >= 8github
全局安裝算法
yarn global add picgo # 或者 npm install picgo -g 複製代碼
使用插件模板npm
picgo init plugin <your-project-name>
複製代碼
picgo-plugin-xxx
的方式命名picgo 是個上傳的流程系統。所以插件其實就是針對這個流程系統的某個部件或者某些部件的開發。api
附一下流程圖:bash
其中能夠供開發的部件總共有5個:markdown
兩個模塊:
- Transformer
- Uploader
三個生命週期插件入口:
- beforeTransformPlugins
- beforeUploadPlugins
- afterUploadPlugins
一般來講若是你只是要實現一個 picgo 默認不支持的圖牀的話,你只須要開發一個
Uploader
。
咱們這裏只是開發圖牀的話就只須要開發 Uploader
便可。
這裏定位到項目的 src/index.ts
或 src/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 生成上傳時的簽名。
首先觀察 strToSign
:
strToSign = Verb + "\n" + Content-MD5 + "\n" + Content-Type + "\n" + Date + "\n" (+ Canonicalized Headers + "\n") + Canonicalized Resource 複製代碼
這裏只上傳圖片,Verb
就是 PUT
,Date
使用 new Date().toUTCString()
。
考慮到簽名的複雜程度,上傳時不發送 Content-MD5 和 Content-Type 請求頭以下降簽名方法的複雜度。
而後就是 Canonicalized Headers
:
Canonicalized Headers 表明請求頭中以 x-qs- 開頭的字段。若是該值爲空,不保留空白行
這種自定義的請求頭確定是沒有的,也能夠去掉。
Canonicalized Resource 表明請求訪問的資源
默認形式:/bucketName/path/fileName
考慮到 path
和 fileName
可能的中文狀況,須要對其 encode 一下。
對 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}` } 複製代碼
對於配置了 customUrl
的私有云用戶,須要獲取到 protocol
和 host
。
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 } } 複製代碼
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 } } 複製代碼
組合上述方法,處理上傳邏輯
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 } } 複製代碼
先登陸 npm 帳號
npm login
複製代碼
發佈到 npm 上就能夠了
npm publish
複製代碼