Skip to content

Commit

Permalink
feat: support aliyun
Browse files Browse the repository at this point in the history
  • Loading branch information
terwer committed Mar 26, 2024
1 parent 32ace45 commit 5e09220
Show file tree
Hide file tree
Showing 8 changed files with 542 additions and 137 deletions.
5 changes: 4 additions & 1 deletion libs/Universal-PicGo-Core/custom.d.ts
@@ -1,4 +1,7 @@
declare module '*.json' {
const value: { [key: string]: any }
export default value
}
}

declare module "ali-oss"
declare module "arraybuffer-to-buffer"
1 change: 1 addition & 0 deletions libs/Universal-PicGo-Core/package.json
Expand Up @@ -32,6 +32,7 @@
},
"dependencies": {
"@picgo/i18n": "^1.0.0",
"ali-oss": "^6.20.0",
"axios": "^1.6.8",
"dayjs": "^1.11.10",
"js-yaml": "^4.1.0",
Expand Down
73 changes: 73 additions & 0 deletions libs/Universal-PicGo-Core/src/plugins/uploader/aliyun-node.ts
@@ -0,0 +1,73 @@
// noinspection ES6PreferShortImport

import { IAliyunConfig, IPicGo } from "../../types"
import { IBuildInEvent } from "../../utils/enums"
import { ILocalesKey } from "../../i18n/zh-CN"
import { base64ToBuffer } from "../../utils/common"
import OSS from "ali-oss"

const handleNode = async (ctx: IPicGo): Promise<IPicGo> => {
const aliYunOptions = ctx.getConfig<IAliyunConfig>("picBed.aliyun")
if (!aliYunOptions) {
throw new Error("Can't find aliYun OSS config")
}

const store = new OSS({
region: aliYunOptions.area,
accessKeyId: aliYunOptions.accessKeyId,
accessKeySecret: aliYunOptions.accessKeySecret,
bucket: aliYunOptions.bucket,
})

const imgList = ctx.output
const customUrl = aliYunOptions.customUrl
const path = aliYunOptions.path

for (const img of imgList) {
if (img.fileName) {
let image = img.buffer
if (!image && img.base64Image) {
image = base64ToBuffer(img.base64Image)
}
if (!image) {
ctx.log.error("Can not find image buffer")
throw new Error("Can not find image buffer")
}
try {
const optionUrl = aliYunOptions.options || ""
const remotePath = `${path}${img.fileName}${optionUrl}`

const result = await store.put(remotePath, new Blob([image]))
console.log("Using aliyun SDK for upload, result=>", result)

if (result?.res?.status && result.res.status === 200) {
delete img.base64Image
delete img.buffer
if (customUrl) {
img.imgUrl = `${customUrl}/${path}${img.fileName}${optionUrl}`
} else {
img.imgUrl = `https://${aliYunOptions.bucket}.${aliYunOptions.area}.aliyuncs.com/${path}${img.fileName}${optionUrl}`
}
} else {
throw new Error("Upload failed")
}
} catch (e: any) {
let errMsg: any
if (e?.statusCode) {
errMsg = e.response?.body ?? e.stack ?? "unknown error"
} else {
errMsg = e.toString()
}
ctx.log.error(errMsg)
ctx.emit(IBuildInEvent.NOTIFICATION, {
title: ctx.i18n.translate<ILocalesKey>("UPLOAD_FAILED"),
body: ctx.i18n.translate<ILocalesKey>("CHECK_SETTINGS"),
})
throw errMsg
}
}
}
return ctx
}

export { handleNode }
100 changes: 100 additions & 0 deletions libs/Universal-PicGo-Core/src/plugins/uploader/aliyun-web.ts
@@ -0,0 +1,100 @@
import { IAliyunConfig, IPicGo } from "../../types"
import crypto from "crypto"
import mime from "mime-types"
import { IBuildInEvent } from "../../utils/enums"
import { ILocalesKey } from "../../i18n/zh-CN"
import { AxiosRequestConfig } from "axios"
import { base64ToBuffer, safeParse } from "../../utils/common"

// generate OSS signature
const generateSignature = (options: IAliyunConfig, fileName: string): string => {
const date = new Date().toUTCString()
const mimeType = mime.lookup(fileName)
if (!mimeType) throw Error(`No mime type found for file ${fileName}`)

const signString = `PUT\n\n${mimeType}\n${date}\n/${options.bucket}/${options.path}${fileName}`

const signature = crypto.createHmac("sha1", options.accessKeySecret).update(signString).digest("base64")
return `OSS ${options.accessKeyId}:${signature}`
}

const postOptions = (
options: IAliyunConfig,
fileName: string,
signature: string,
image: Buffer
): AxiosRequestConfig => {
const xCorsHeaders = {
Host: `${options.bucket}.${options.area}.aliyuncs.com`,
Date: new Date().toUTCString(),
}

return {
method: "PUT",
url: `https://${options.bucket}.${options.area}.aliyuncs.com/${encodeURI(options.path)}${encodeURI(fileName)}`,
headers: {
Authorization: signature,
"Content-Type": mime.lookup(fileName),
"x-cors-headers": JSON.stringify(xCorsHeaders),
},
data: image,
resolveWithFullResponse: true,
} as AxiosRequestConfig
}

const handleWeb = async (ctx: IPicGo): Promise<IPicGo> => {
const aliYunOptions = ctx.getConfig<IAliyunConfig>("picBed.aliyun")
if (!aliYunOptions) {
throw new Error("Can't find aliYun OSS config")
}

const imgList = ctx.output
const customUrl = aliYunOptions.customUrl
const path = aliYunOptions.path
for (const img of imgList) {
if (img.fileName) {
let image = img.buffer
if (!image && img.base64Image) {
image = base64ToBuffer(img.base64Image)
}
if (!image) {
ctx.log.error("Can not find image buffer")
throw new Error("Can not find image buffer")
}
try {
const signature = generateSignature(aliYunOptions, img.fileName)
const options = postOptions(aliYunOptions, img.fileName, signature, image)
const res: any = await ctx.request(options)
const body = safeParse<any>(res)
if (body.statusCode === 200) {
delete img.base64Image
delete img.buffer
const optionUrl = aliYunOptions.options || ""
if (customUrl) {
img.imgUrl = `${customUrl}/${path}${img.fileName}${optionUrl}`
} else {
img.imgUrl = `https://${aliYunOptions.bucket}.${aliYunOptions.area}.aliyuncs.com/${path}${img.fileName}${optionUrl}`
}
} else {
throw new Error("Upload failed")
}
} catch (e: any) {
let errMsg: any
if (e?.statusCode) {
errMsg = e.response?.body ?? e.stack ?? "unknown error"
} else {
errMsg = e.toString()
}
ctx.log.error(errMsg)
ctx.emit(IBuildInEvent.NOTIFICATION, {
title: ctx.i18n.translate<ILocalesKey>("UPLOAD_FAILED"),
body: ctx.i18n.translate<ILocalesKey>("CHECK_SETTINGS"),
})
throw errMsg
}
}
}
return ctx
}

export { handleWeb }
117 changes: 15 additions & 102 deletions libs/Universal-PicGo-Core/src/plugins/uploader/aliyun.ts
@@ -1,101 +1,14 @@
import { IAliyunConfig, IPicGo, IPluginConfig } from "../../types"
import crypto from "crypto"
import mime from "mime-types"
import { IBuildInEvent } from "../../utils/enums"
import { ILocalesKey } from "../../i18n/zh-CN"
import { AxiosRequestConfig } from "axios"
import { base64ToBuffer, bufferToBase64, safeParse } from "../../utils/common"

// generate OSS signature
const generateSignature = (options: IAliyunConfig, fileName: string): string => {
const date = new Date().toUTCString()
const mimeType = mime.lookup(fileName)
if (!mimeType) throw Error(`No mime type found for file ${fileName}`)

const signString = `PUT\n\n${mimeType}\n${date}\n/${options.bucket}/${options.path}${fileName}`

const signature = crypto.createHmac("sha1", options.accessKeySecret).update(signString).digest("base64")
return `OSS ${options.accessKeyId}:${signature}`
}

const postOptions = (
options: IAliyunConfig,
fileName: string,
signature: string,
image: Buffer
): AxiosRequestConfig => {
const xCorsHeaders = {
Host: `${options.bucket}.${options.area}.aliyuncs.com`,
Date: new Date().toUTCString(),
}

return {
method: "PUT",
url: `https://${options.bucket}.${options.area}.aliyuncs.com/${encodeURI(options.path)}${encodeURI(fileName)}`,
headers: {
Authorization: signature,
"Content-Type": mime.lookup(fileName),
// "x-cors-headers": JSON.stringify(xCorsHeaders),
},
data: image,
resolveWithFullResponse: true,
proxy: "must" as any,
} as AxiosRequestConfig
}
import { hasNodeEnv } from "universal-picgo-store"
import { handleNode } from "./aliyun-node"
import { handleWeb } from "./aliyun-web"

const handle = async (ctx: IPicGo): Promise<IPicGo> => {
const aliYunOptions = ctx.getConfig<IAliyunConfig>("picBed.aliyun")
if (!aliYunOptions) {
throw new Error("Can't find aliYun OSS config")
if (hasNodeEnv) {
return handleNode(ctx)
}

const imgList = ctx.output
const customUrl = aliYunOptions.customUrl
const path = aliYunOptions.path
for (const img of imgList) {
if (img.fileName) {
let image = img.buffer
if (!image && img.base64Image) {
image = base64ToBuffer(img.base64Image)
}
if (!image) {
ctx.log.error("Can not find image buffer")
throw new Error("Can not find image buffer")
}
try {
const signature = generateSignature(aliYunOptions, img.fileName)
const options = postOptions(aliYunOptions, img.fileName, signature, image)
const res: any = await ctx.request(options)
const body = safeParse<any>(res)
if (body.statusCode === 200) {
delete img.base64Image
delete img.buffer
const optionUrl = aliYunOptions.options || ""
if (customUrl) {
img.imgUrl = `${customUrl}/${path}${img.fileName}${optionUrl}`
} else {
img.imgUrl = `https://${aliYunOptions.bucket}.${aliYunOptions.area}.aliyuncs.com/${path}${img.fileName}${optionUrl}`
}
} else {
throw new Error("Upload failed")
}
} catch (e: any) {
let errMsg: any
if (e?.statusCode) {
errMsg = e.response?.body ?? e.stack ?? "unknown error"
} else {
errMsg = e.toString()
}
ctx.log.error(errMsg)
ctx.emit(IBuildInEvent.NOTIFICATION, {
title: ctx.i18n.translate<ILocalesKey>("UPLOAD_FAILED"),
body: ctx.i18n.translate<ILocalesKey>("CHECK_SETTINGS"),
})
throw errMsg
}
}
}
return ctx
return handleWeb(ctx)
}

const config = (ctx: IPicGo): IPluginConfig[] => {
Expand All @@ -108,7 +21,7 @@ const config = (ctx: IPicGo): IPluginConfig[] => {
return ctx.i18n.translate<ILocalesKey>("PICBED_ALICLOUD_ACCESSKEYID")
},
default: userConfig.accessKeyId || "",
required: true,
required: true
},
{
name: "accessKeySecret",
Expand All @@ -117,7 +30,7 @@ const config = (ctx: IPicGo): IPluginConfig[] => {
return ctx.i18n.translate<ILocalesKey>("PICBED_ALICLOUD_ACCESSKEYSECRET")
},
default: userConfig.accessKeySecret || "",
required: true,
required: true
},
{
name: "bucket",
Expand All @@ -126,7 +39,7 @@ const config = (ctx: IPicGo): IPluginConfig[] => {
return ctx.i18n.translate<ILocalesKey>("PICBED_ALICLOUD_BUCKET")
},
default: userConfig.bucket || "",
required: true,
required: true
},
{
name: "area",
Expand All @@ -141,7 +54,7 @@ const config = (ctx: IPicGo): IPluginConfig[] => {
get message() {
return ctx.i18n.translate<ILocalesKey>("PICBED_ALICLOUD_MESSAGE_AREA")
},
required: true,
required: true
},
{
name: "path",
Expand All @@ -156,7 +69,7 @@ const config = (ctx: IPicGo): IPluginConfig[] => {
return ctx.i18n.translate<ILocalesKey>("PICBED_ALICLOUD_MESSAGE_PATH")
},
default: userConfig.path || "",
required: false,
required: false
},
{
name: "customUrl",
Expand All @@ -171,7 +84,7 @@ const config = (ctx: IPicGo): IPluginConfig[] => {
return ctx.i18n.translate<ILocalesKey>("PICBED_ALICLOUD_MESSAGE_CUSTOMURL")
},
default: userConfig.customUrl || "",
required: false,
required: false
},
{
name: "options",
Expand All @@ -186,8 +99,8 @@ const config = (ctx: IPicGo): IPluginConfig[] => {
return ctx.i18n.translate<ILocalesKey>("PICBED_ALICLOUD_MESSAGE_OPTIONS")
},
default: userConfig.options || "",
required: false,
},
required: false
}
]
return config
}
Expand All @@ -198,6 +111,6 @@ export default function register(ctx: IPicGo): void {
return ctx.i18n.translate<ILocalesKey>("PICBED_ALICLOUD")
},
handle,
config,
config
})
}
Expand Up @@ -14,7 +14,7 @@ import { IPluginConfig } from "zhi-siyuan-picgo"
import { useVueI18n } from "$composables/useVueI18n.ts"
import { createAppLogger } from "@/utils/appLogger.ts"
import _ from "lodash-es"
import { PicgoHelper } from "zhi-siyuan-picgo/src"
import { PicgoHelper } from "zhi-siyuan-picgo"
const logger = createAppLogger("picbed-config-form")
const { t } = useVueI18n()
Expand Down
Expand Up @@ -10,7 +10,7 @@
<script setup lang="ts">
import { ImageItem } from "zhi-siyuan-picgo/src/lib/models/ImageItem.ts"
import { BrowserUtil } from "zhi-device"
import { copyToClipboardInBrowser } from "zhi-siyuan-picgo/src"
import { copyToClipboardInBrowser } from "zhi-siyuan-picgo"
import { useVueI18n } from "$composables/useVueI18n.ts"
import { reactive } from "vue"
Expand Down

0 comments on commit 5e09220

Please sign in to comment.