Worker R2 图片存储最优解

尺码哥 最近在微调一个模型,需要用到很多图片来训练,同时自动化测试的时候,也会产生很多图片,想直接放到腾讯云的 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');
}

注意事项

一定要自定义域名,不然有些地方不能访问,自定义域名好像问题不大。

发表评论