尺码哥 最近在微调一个模型,需要用到很多图片来训练,同时自动化测试的时候,也会产生很多图片,想直接放到腾讯云的 COS 里面,但是 COS 调整了策略,只有备案的域名才能直接预览,不然默认变成下载了,我就看了一下 cloudflare 的 Worker R2 的方案,确实很方便。
场景
我找了几个给我标注图片的人,他们标注完成的图片,要实时保存,要在浏览器里面修改,立马就要保存到服务器,而我不想自己通过服务端中转,想直接存储到云存储如 COS 或者 S3 相关的存储中。
有时候生成的图片,通过命令行直接 curl 上传,不想搞复杂的token 认证。
方案
腾讯云的我测试了一下,满足不了我的场景,并且要开这个服务,那个服务,这些都是收费的,我完成付费后,最后一个预览就不行,要备案的域名,不得不选择别的方案。
然后就直接上传到 R2,我看了一下相关的 Api,虽然是兼容S3 协议,但是让 Cursor 和 claude 更改了几次,都遇到各种问题。
然后看了一下很多人用 Worker 和 R2 直接关联,确实能满足我的要求。
Worker
Worker 我直接让 Cluade 给我写的代码,同时参考了官方的示例。
代码没有多少,并且还加了一个简单的权限验证。
const ALLOW_LIST = ["cat-pic.jpg"];
function corsHeaders(request) {
return {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET,HEAD,POST,PUT,DELETE,OPTIONS",
"Access-Control-Allow-Headers": "Content-Type,X-Custom-Auth-Key",
"Access-Control-Max-Age": "86400",
};
}
// Check requests for a pre-shared secret
const hasValidHeader = (request, env) => {
return request.headers.get("X-Custom-Auth-Key") === env.AUTH_KEY_SECRET;
};
function authorizeRequest(request, env, key) {
switch (request.method) {
case "PUT":
case "DELETE":
return hasValidHeader(request, env);
case "GET":
//return ALLOW_LIST.includes(key);
return true;
default:
return false;
}
}
export default {
async fetch(request, env) {
// 处理 OPTIONS 请求
if (request.method === "OPTIONS") {
return new Response(null, {
headers: corsHeaders(request),
});
}
const url = new URL(request.url);
const key = url.pathname.slice(1);
if (!authorizeRequest(request, env, key)) {
return new Response("Forbidden", {
status: 403,
headers: corsHeaders(request)
});
}
switch (request.method) {
case "PUT":
await env.MY_BUCKET.put(key, request.body);
return new Response(`Put ${key} successfully!`, {
headers: corsHeaders(request)
});
case "GET":
const object = await env.MY_BUCKET.get(key);
if (object === null) {
return new Response("Object Not Found", {
status: 404,
headers: corsHeaders(request)
});
}
const headers = new Headers(corsHeaders(request));
object.writeHttpMetadata(headers);
headers.set("etag", object.httpEtag);
return new Response(object.body, {
headers,
});
case "DELETE":
await env.MY_BUCKET.delete(key);
return new Response("Deleted!", {
headers: corsHeaders(request)
});
default:
return new Response("Method Not Allowed", {
status: 405,
headers: {
...corsHeaders(request),
Allow: "PUT, GET, DELETE, OPTIONS",
},
});
}
},
};
主要是加了一些跨域的逻辑在里面,不然在浏览器通过 Javascrip 上传图片的时候,会碰到跨域的问题。
R2
R2 正确的建就可以,没有什么要特别注意的。
javascrpt 上传图片
// 生成图片并上传到指定服务
async function generateAndUploadImage() {
const canvas = document.createElement('canvas');
canvas.width = 300;
canvas.height = 300;
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'black';
ctx.font = '30px Arial';
ctx.fillText('hello world', 50, 150);
// 生成独一无二的文件名
const uniqueFileName = generateUniqueFileName();
canvas.toBlob(function(blob) {
const formData = new FormData();
formData.append('file', blob, uniqueFileName);
fetch(`https://worker.abcc.com/${uniqueFileName}`, {
method: 'PUT',
headers: {
'X-Custom-Auth-Key': 'key'
},
body: blob
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.text();
})
.then(data => {
console.log('Success:', data);
alert(`Image uploaded successfully as ${uniqueFileName}!`);
})
.catch((error) => {
console.error('Error:', error);
alert('Error uploading image: ' + error.message);
});
}, 'image/jpeg');
}
注意事项
一定要自定义域名,不然有些地方不能访问,自定义域名好像问题不大。