mirror of
https://github.com/doocs/md.git
synced 2025-01-23 04:14:42 +08:00
feat: add support for Cloudflare R2 using AWS S3 API (#484)
All checks were successful
Build and Deploy / build-and-deploy (push) Has been skipped
All checks were successful
Build and Deploy / build-and-deploy (push) Has been skipped
This commit is contained in:
parent
1d6ab54091
commit
f10c5e665f
@ -44,7 +44,7 @@ Markdown 文档自动即时渲染为微信图文,让你不再为微信文章
|
|||||||
## 目前支持哪些图床
|
## 目前支持哪些图床
|
||||||
|
|
||||||
| # | 图床 | 使用时是否需要配置 | 备注 |
|
| # | 图床 | 使用时是否需要配置 | 备注 |
|
||||||
| --- | ----------------------------------------------- | -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
| --- | ------------------------------------------------------ | -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| 1 | 默认 | 否 | - |
|
| 1 | 默认 | 否 | - |
|
||||||
| 2 | [GitHub](https://github.com) | 配置 `Repo`、`Token` 参数 | [如何获取 GitHub token?](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) |
|
| 2 | [GitHub](https://github.com) | 配置 `Repo`、`Token` 参数 | [如何获取 GitHub token?](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) |
|
||||||
| 3 | [阿里云](https://www.aliyun.com/product/oss) | 配置 `AccessKey ID`、`AccessKey Secret`、`Bucket`、`Region` 参数 | [如何使用阿里云 OSS?](https://help.aliyun.com/document_detail/31883.html) |
|
| 3 | [阿里云](https://www.aliyun.com/product/oss) | 配置 `AccessKey ID`、`AccessKey Secret`、`Bucket`、`Region` 参数 | [如何使用阿里云 OSS?](https://help.aliyun.com/document_detail/31883.html) |
|
||||||
@ -52,7 +52,8 @@ Markdown 文档自动即时渲染为微信图文,让你不再为微信文章
|
|||||||
| 5 | [七牛云](https://www.qiniu.com/products/kodo) | 配置 `AccessKey`、`SecretKey`、`Bucket`、`Domain`、`Region` 参数 | [如何使用七牛云 Kodo?](https://developer.qiniu.com/kodo) |
|
| 5 | [七牛云](https://www.qiniu.com/products/kodo) | 配置 `AccessKey`、`SecretKey`、`Bucket`、`Domain`、`Region` 参数 | [如何使用七牛云 Kodo?](https://developer.qiniu.com/kodo) |
|
||||||
| 6 | [MinIO](https://min.io/) | 配置 `Endpoint`、`Port`、`UseSSL`、`Bucket`、`AccessKey`、`SecretKey` 参数 | [如何使用 MinIO?](http://docs.minio.org.cn/docs/master/) |
|
| 6 | [MinIO](https://min.io/) | 配置 `Endpoint`、`Port`、`UseSSL`、`Bucket`、`AccessKey`、`SecretKey` 参数 | [如何使用 MinIO?](http://docs.minio.org.cn/docs/master/) |
|
||||||
| 7 | [公众号](https://mp.weixin.qq.com/) | 配置 `appID`、`appsecret`、`代理域名` 参数 | [如何获取公众号开发者 ID 密码?](https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Getting_Started_Guide.html) |
|
| 7 | [公众号](https://mp.weixin.qq.com/) | 配置 `appID`、`appsecret`、`代理域名` 参数 | [如何获取公众号开发者 ID 密码?](https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Getting_Started_Guide.html) |
|
||||||
| 8 | 自定义上传 | 是 | [如何自定义上传?](#自定义上传逻辑) |
|
| 8 | [Cloudflare R2](https://developers.cloudflare.com/r2/) | 配置 `AccountId`、`AccessKey`、`SecretKey`、`Bucket`、`Domain` 参数 | [如何使用 S3 API 操作 R2](https://developers.cloudflare.com/r2/api/s3/api/) |
|
||||||
|
| 9 | 自定义上传 | 是 | [如何自定义上传?](#自定义上传逻辑) |
|
||||||
|
|
||||||
![demo1](https://cdn-doocs.oss-cn-shenzhen.aliyuncs.com/gh/doocs/md/images/demo1.gif)
|
![demo1](https://cdn-doocs.oss-cn-shenzhen.aliyuncs.com/gh/doocs/md/images/demo1.gif)
|
||||||
|
|
||||||
|
1645
package-lock.json
generated
1645
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -21,6 +21,7 @@
|
|||||||
"postinstall": "simple-git-hooks && wxt prepare"
|
"postinstall": "simple-git-hooks && wxt prepare"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@aws-sdk/client-s3": "^3.716.0",
|
||||||
"@vueuse/core": "^12.0.0",
|
"@vueuse/core": "^12.0.0",
|
||||||
"axios": "^1.7.8",
|
"axios": "^1.7.8",
|
||||||
"buffer-from": "^1.1.2",
|
"buffer-from": "^1.1.2",
|
||||||
|
@ -56,6 +56,15 @@ const minioOSS = ref({
|
|||||||
secretKey: ``,
|
secretKey: ``,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const formR2 = ref({
|
||||||
|
accountId: ``,
|
||||||
|
accessKey: ``,
|
||||||
|
secretKey: ``,
|
||||||
|
bucket: ``,
|
||||||
|
domain: ``,
|
||||||
|
path: ``,
|
||||||
|
})
|
||||||
|
|
||||||
const formMp = ref({
|
const formMp = ref({
|
||||||
proxyOrigin: ``,
|
proxyOrigin: ``,
|
||||||
appID: ``,
|
appID: ``,
|
||||||
@ -96,6 +105,10 @@ const options = [
|
|||||||
value: `mp`,
|
value: `mp`,
|
||||||
label: `公众号图床`,
|
label: `公众号图床`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
value: `r2`,
|
||||||
|
label: `Cloudflare R2`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
value: `formCustom`,
|
value: `formCustom`,
|
||||||
label: `自定义代码`,
|
label: `自定义代码`,
|
||||||
@ -125,6 +138,9 @@ onBeforeMount(() => {
|
|||||||
if (localStorage.getItem(`minioConfig`)) {
|
if (localStorage.getItem(`minioConfig`)) {
|
||||||
minioOSS.value = JSON.parse(localStorage.getItem(`minioConfig`)!)
|
minioOSS.value = JSON.parse(localStorage.getItem(`minioConfig`)!)
|
||||||
}
|
}
|
||||||
|
if (localStorage.getItem(`r2Config`)) {
|
||||||
|
formR2.value = JSON.parse(localStorage.getItem(`r2Config`)!)
|
||||||
|
}
|
||||||
if (localStorage.getItem(`imgHost`)) {
|
if (localStorage.getItem(`imgHost`)) {
|
||||||
imgHost.value = localStorage.getItem(`imgHost`)!
|
imgHost.value = localStorage.getItem(`imgHost`)!
|
||||||
}
|
}
|
||||||
@ -220,6 +236,23 @@ function saveQiniuConfiguration() {
|
|||||||
toast.success(`保存成功`)
|
toast.success(`保存成功`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function saveR2Configuration() {
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
formR2.value.accountId
|
||||||
|
&& formR2.value.accessKey
|
||||||
|
&& formR2.value.secretKey
|
||||||
|
&& formR2.value.bucket
|
||||||
|
&& formR2.value.domain
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
toast.error(`Cloudflare R2参数配置不全`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
localStorage.setItem(`r2Config`, JSON.stringify(formR2.value))
|
||||||
|
toast.success(`保存成功`)
|
||||||
|
}
|
||||||
|
|
||||||
function saveMpConfiguration() {
|
function saveMpConfiguration() {
|
||||||
if (
|
if (
|
||||||
!(
|
!(
|
||||||
@ -315,6 +348,9 @@ function onDrop(e: DragEvent) {
|
|||||||
<TabsTrigger value="mp">
|
<TabsTrigger value="mp">
|
||||||
公众号图床
|
公众号图床
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="r2">
|
||||||
|
Cloudflare R2
|
||||||
|
</TabsTrigger>
|
||||||
<TabsTrigger value="formCustom">
|
<TabsTrigger value="formCustom">
|
||||||
自定义代码
|
自定义代码
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
@ -689,6 +725,58 @@ function onDrop(e: DragEvent) {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
<TabsContent value="r2">
|
||||||
|
<div class="space-y-4">
|
||||||
|
<FormItem label="AccountId" required>
|
||||||
|
<Input v-model.trim="formR2.accountId" placeholder="如: 0030f123e55a57546f4c281c564e560" class="min-w-[350px]" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="AccessKey" required>
|
||||||
|
<Input v-model.trim="formR2.accessKey" placeholder="如: 358090b3a12824a6b0787gae7ad0fc72" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="SecretKey" required>
|
||||||
|
<Input
|
||||||
|
v-model.trim="formR2.secretKey" type="password"
|
||||||
|
placeholder="如: c1c4dbcb0b6b785ac6633422a06dff3dac055fe74fe40xj1b5c5fcf1bf128010"
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="Bucket" required>
|
||||||
|
<Input v-model.trim="formR2.bucket" placeholder="如:md" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="域名" required>
|
||||||
|
<Input v-model.trim="formR2.domain" placeholder="如:https://oss.example.com" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="存储路径">
|
||||||
|
<Input v-model.trim="formR2.path" placeholder="如:img,可不填,默认为根目录" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem>
|
||||||
|
<div class="flex flex-col items-start">
|
||||||
|
<Button
|
||||||
|
variant="link"
|
||||||
|
class="p-0"
|
||||||
|
as="a"
|
||||||
|
href="https://developers.cloudflare.com/r2/api/s3/api/"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
如何使用 S3 API 操作 Cloudflare R2
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="link"
|
||||||
|
class="p-0"
|
||||||
|
as="a"
|
||||||
|
href="https://developers.cloudflare.com/r2/buckets/cors/"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
如何设置跨域(CORS)
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem>
|
||||||
|
<Button @click="saveR2Configuration">
|
||||||
|
保存配置
|
||||||
|
</Button>
|
||||||
|
</FormItem>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
<TabsContent value="formCustom">
|
<TabsContent value="formCustom">
|
||||||
<CustomUploadForm />
|
<CustomUploadForm />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
@ -3,6 +3,7 @@ import fetch from '@/utils/fetch'
|
|||||||
import * as tokenTools from '@/utils/tokenTools'
|
import * as tokenTools from '@/utils/tokenTools'
|
||||||
|
|
||||||
import { base64encode, safe64, utf16to8 } from '@/utils/tokenTools'
|
import { base64encode, safe64, utf16to8 } from '@/utils/tokenTools'
|
||||||
|
import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3'
|
||||||
import Buffer from 'buffer-from'
|
import Buffer from 'buffer-from'
|
||||||
import COS from 'cos-js-sdk-v5'
|
import COS from 'cos-js-sdk-v5'
|
||||||
import CryptoJS from 'crypto-js'
|
import CryptoJS from 'crypto-js'
|
||||||
@ -376,6 +377,33 @@ async function mpFileUpload(file: File) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Cloudflare R2 File Upload
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
async function r2Upload(file: File) {
|
||||||
|
const { accountId, accessKey, secretKey, bucket, path, domain } = JSON.parse(
|
||||||
|
localStorage.getItem(`r2Config`)!,
|
||||||
|
)
|
||||||
|
const dir = path ? `${path}/` : ``
|
||||||
|
const filename = dir + getDateFilename(file.name)
|
||||||
|
const client = new S3Client({ region: `auto`, endpoint: `https://${accountId}.r2.cloudflarestorage.com`, credentials: { accessKeyId: accessKey, secretAccessKey: secretKey } })
|
||||||
|
|
||||||
|
return new Promise<string>((resolve, reject) => {
|
||||||
|
const putObjectCommand = new PutObjectCommand({
|
||||||
|
Bucket: bucket,
|
||||||
|
Key: filename,
|
||||||
|
ContentType: file.type,
|
||||||
|
Body: file,
|
||||||
|
})
|
||||||
|
client.send(putObjectCommand).then(() => {
|
||||||
|
resolve(`${domain}/${filename}`)
|
||||||
|
}).catch((err) => {
|
||||||
|
reject(err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
// formCustom File Upload
|
// formCustom File Upload
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
@ -433,6 +461,8 @@ function fileUpload(content: string, file: File) {
|
|||||||
return ghFileUpload(content, file.name)
|
return ghFileUpload(content, file.name)
|
||||||
case `mp`:
|
case `mp`:
|
||||||
return mpFileUpload(file)
|
return mpFileUpload(file)
|
||||||
|
case `r2`:
|
||||||
|
return r2Upload(file)
|
||||||
case `formCustom`:
|
case `formCustom`:
|
||||||
return formCustomUpload(content, file)
|
return formCustomUpload(content, file)
|
||||||
default:
|
default:
|
||||||
|
Loading…
Reference in New Issue
Block a user