前言
本站原本使用 SMMS 作为 Twikoo 评论的床图存储服务,但手贱把账号注销了😅。想重新注册却发现 SMMS 已关闭注册。
看了其他几种床图方案,要么收费,要么需要自己搭建服务器。
正好最近博客大改,图片都放在 R2,于是想:能不能写一个 Worker 来模拟这些服务提供的接口?
在各大文档转了一圈后发现,EasyImages 的接口最简单😎,所以决定直接用 EasyImages 的 API。
官方的API文档:EasyImages 2.0 API
Worker代码
EasyImages API 的上传流程非常简单:直接发送 token 和 image 即可。
例如前端上传表单:
<form action="http://127.0.0.1/api/index.php" method="post" enctype="multipart/form-data">
<input type="file" name="image" accept="image/*" required>
<input type="text" name="token" placeholder="在tokenList文件找到token并输入" required>
<input type="submit" value="上传">
</form>
Worker 接收到请求后,验证 token,然后上传到 R2,返回格式与 EasyImages API 一致,例如:
{
"result":"success",
"code":200,
"url":"https:\/\/i2.100024.xyz\/2023\/01\/24\/10gwv0y-0.webp",
"srcName":"195124",
"thumb":"https:\/\/png.cm\/application\/thumb.php?img=\/i\/2023\/01\/24\/10gwv0y-0.webp",
"del":"https:\/\/png.cm\/application\/del.php?hash=bW8vWG4vcG8yM2pLQzRJUGI0dHlTZkN4L2grVmtwUTFhd1A4czJsbHlMST0="
}
为了防止滥用,可以在 Worker 中添加 访问速率控制,把访问 IP 保存到 KV。
下面是完整代码:
const customDomain = "https://cdn.zhengweixin.top/" // 访问域名
const prefix = "blog/comments/" // 上传路径
const TOKEN = "123456" // API Token
const RATE_LIMIT_WINDOW = 3600_000 // 上传限制时间
const RATE_LIMIT_MAX = 5 // 时间内可上传的次数
// 以上配置自行修改,并绑定KV和R2至该worker
export default {
async fetch(request, env) {
if (request.method !== "POST") {
return new Response("Method Not Allowed", { status: 405 })
}
const ip = request.headers.get("CF-Connecting-IP") || "unknown"
const limitKey = `upload:${ip}`
const limitData = await env.KV.get(limitKey, { type: "json" }) || {
count: 0,
reset: Date.now() + RATE_LIMIT_WINDOW
}
if (Date.now() > limitData.reset) {
limitData.count = 0
limitData.reset = Date.now() + RATE_LIMIT_WINDOW
}
if (limitData.count >= RATE_LIMIT_MAX) {
const retryAfter = Math.ceil((limitData.reset - Date.now()) / 1000)
return new Response(JSON.stringify({
result: "error",
code: 429,
message: `每小时只能上传 ${RATE_LIMIT_MAX} 次, 请稍后再试或自行找床图上传!`
}), {
headers: {
"Content-Type": "application/json",
"Retry-After": retryAfter.toString()
},
status: 429
})
}
limitData.count++
await env.KV.put(limitKey, JSON.stringify(limitData), { expirationTtl: RATE_LIMIT_WINDOW / 1000 })
const reqClone = request.clone()
const formData = await reqClone.formData()
const file = formData.get("image")
const token = formData.get("token")
if (!token || token !== TOKEN) {
return new Response(JSON.stringify({
result: "error",
code: 403,
message: "无效的上传令牌"
}), { headers: { "Content-Type": "application/json" }, status: 403 })
}
if (!file) {
return new Response(JSON.stringify({ result: "error", code: 400, message: "没有文件上传" }), {
headers: { "Content-Type": "application/json" },
status: 400
})
}
const arrayBuffer = await file.arrayBuffer()
const ext = file.name.includes(".") ? file.name.substring(file.name.lastIndexOf(".")) : ""
const timestamp = Date.now()
const randomId = ('randomUUID' in crypto
? crypto.randomUUID()
: Array.from(crypto.getRandomValues(new Uint8Array(16)).map(b => b.toString(16).padStart(2,'0')))
).replace(/-/g,'')
const key = `${prefix}${timestamp}_${randomId}${ext}`
try {
await env.R2.put(key, arrayBuffer, { httpMetadata: { contentType: file.type } })
return new Response(JSON.stringify({
result: "success",
code: 200,
url: `${customDomain}${key}`,
srcName: file.name,
thumb: `${customDomain}${key}`,
del: `${customDomain}del?key=${key}`
}), {
headers: { "Content-Type": "application/json" }
})
} catch (err) {
return new Response(JSON.stringify({ result: "error", code: 500, message: err.message }), {
headers: { "Content-Type": "application/json" },
status: 500
})
}
}
}
我也上传 Github 了:easyimg-api-r2
使用
代码开头的那些信息替换成自己的,然后部署到worker,并绑定R2和VK。
部署后前往 Twikoo 设置,IMAGE_CDN 填写 easyimage ,IMAGE_CDN_URL 填 Worker 的域名,IMAGE_CDN_TOKEN 填代码开头设置的 TOKEN 然后保存即可使用。
然后就可以在评论上传了!
到这里,你就有拥有了一个 10G 的免费高速的床图了,而且高达1000w次的读取额度和100w次的上传,完全是够用了。
而且还可以在我的博客后台进行管理,非常的方便。
当然,理论上在其他平台的云函数服务也可以部署,或者使用其他s3存储,但远远不及 Worker 直接绑定 R2 来的简单。

