前言
twikoo
的评论需要用到的上传图床基本都有各种限制,比如sm.ms
只提供5GB的存储,7bu
图床需要花钱买,qcloud
是腾讯的,不太想用而且要花钱,lsky-pro
兰空图床部署需要用到数据库,可是我已经决定用hexo
+twikoo
搭建一个serverless博客了,还要用到数据库,还需要公网ip服务器,这就本末倒置了。
转念一想既然可以对接兰空图床自定义url,那么我就写一个api兼容兰空图床的api,对接cloudflare r2
,这样不就变成免费的10GB额度方案了吗
于是我决定写一个cloudflare worker
+ cloudflare r2
去对接目前lsky-pro
对应的端口,然后填上url,返回对应结果
分析接口返回的数据
查看twikoo
源码,对应的上传图床部分
async uploadImageToLskyPro ({ photo, fileName, config, res, imageCdn }) {
// 自定义兰空图床(v2)URL
const formData = new FormData()
formData.append('file', fn.base64UrlToReadStream(photo, fileName))
if (process.env.TWIKOO_LSKY_STRATEGY_ID) {
formData.append('strategy_id', parseInt(process.env.TWIKOO_LSKY_STRATEGY_ID))
}
const url = `${imageCdn}/api/v1/upload`
let token = config.IMAGE_CDN_TOKEN
if (!token.startsWith('Bearer')) {
token = `Bearer ${token}`
}
const uploadResult = await axios.post(url, formData, {
headers: {
...formData.getHeaders(),
Authorization: token
}
})
if (uploadResult.data.status) {
res.data = uploadResult.data.data
res.data.url = res.data.links.url
} else {
throw new Error(uploadResult.data.message)
}
}
直接定位,接受一个form传参
formData.append('file', fn.base64UrlToReadStream(photo, fileName))
需要的返回值为
uploadResult.data.status
uploadResult.data.data.links.url
返回示例
{
"status": true,
"data": {
"links" : {
"url": "<r2图床地址>"
}
}
}
endpoint | method | data |
---|---|---|
/api/v1/upload | POST | 同上 |
这里还可以加个auth请求头认证防止盗用
分析完了接下来开始写worker
worker代码
首先创建一个新项目
wrangler init twikoo-worker
修改wrangler.toml
的配置,让其绑定r2存储桶及变量
#:schema node_modules/wrangler/config-schema.json
name = "twikoo-worker"
main = "src/index.js"
compatibility_date = "2024-12-05"
compatibility_flags = ["nodejs_compat"]
[observability]
enabled = true
[vars]
BUCKET_URL = "https://bucket.voidval.com"
# 修改你的bucket url,指向你的r2 存储桶
[[r2_buckets]]
binding = "MY_BUCKET"
bucket_name = "bucket1" # 修改为你图床的bucket名称
根据刚才的接口写worker,修改twikoo-worker/src/index.js
const hasValidHeader = (request, env) => {
return request.headers.get("Authorization") === "Bearer " + env.AUTH_KEY_SECRET;
};
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
if (!hasValidHeader(request, env))
return new Response("Not permitted.", { status: 401 });
if (request.method !== "POST") return new Response("Method not permitted.", { status: 401 });
if (url.pathname !== "/api/v1/upload"){
return new Response("Path not permitted", { status: 401 });
}
const formData = await request.formData()
const photo = await formData.get('file')
if (!photo) return new Response("Invalid File upload.", { status: 401 });
await env.MY_BUCKET.put("comment/upload/" + photo.name, photo.stream());
const responseObject = {
"status": true,
"data": {
"links" : {
"url": env.BUCKET_URL + "/comment/upload/" + photo.name
}
}
}
const headers = {
'Access-Control-Allow-Origin': '*', // Or your specific origin
'content-type': 'application/json;charset=UTF-8',
}
return Response.json(responseObject, {
headers: headers
});
},
};
这里对r2存储桶的操作
是根据cloudflare api文档找到的,英语不好很麻烦所以一定要学好英语
现在带着大家分析一下api文档,如果你对此不感兴趣可以跳过
看这个put
的部分,很简单,就一句话await env.MY_BUCKET.put(key, request.body);
很明显这里是向r2存储桶添加文件的api
,如果你很熟悉cloudflare
的话,可以知道env.MY_BUCKET
就是下文wrangler.toml
绑定的存储桶,MY_BUCKET
就是名称
这个binding
指向的就是在代码里能调用的对象,bucket_name
则是存储桶的实际名称
然后看put
的api
定义
可以看到接收key
和value
还有个可选的R2PutOptions
参数,并返回Promise R2Object
对象(我不是学前端的不知道说的对不对,总之大概是这么个意思)
很容易就推理出key
就是存入存储桶的名称,value
就是文件二进制数据流,但问题是保存路径该怎么存储呢?比如我想保存到存储桶下的comment/upload
文件夹下
我当时的思路是看这个R2PutOptions
,但很遗憾没有相关的选项
那该怎么办呢?这里我靠经验把存储桶的key
加上了路径await env.MY_BUCKET.put("comment/upload/" + photo.name, photo.stream());
尝试了一下发现还真能改变路径。看来有些时候确实需要一点误打误撞
以上就是worker的逻辑实现
接下来添加secret token
wrangler secret put AUTH_KEY_SECRET
输入你的auth token
,记录下这个值,后面twikoo填入配置时要用
wrangler deploy
用postman尝试一下
选择form-data
,然后选择文件类型file
,key
填file
(根据前面的formData.append('file', fn.base64UrlToReadStream(photo, fileName))
)
现在去尝试将url填到twikoo
中
保存后尝试上传图片
可以看到上传成功了
至此你就拥有了一个10GB,且不需要任何公网流出流量费用的,读取写入免费额度达到百万级的twikoo
评论图床
虽然只比sm.ms多5G,但也很多了_(:з)∠)_
而且在兼容s3的存储桶中也比较方便管理
理论上也适用各种云函数severless方案,只需要对接存储桶即可