CF+Workers使用B2的私有储存桶

  1. 登录B2创建储存桶→桶设定
  2. 鉴于CloudflareWorkers每天只有(其实很多)10万次免费请求,而且每次都重新请求时效性不高,所以我们可以人CloudflareCDN缓存下来,打开Backblaze,找到你刚刚创建的桶,点击桶设定,在桶信息里添加如下内容:
    {"cache-control":"public, max-age=15552000"}

     

  3. 设置文件的生命周期(如果不设置B2会把所有版本的文件都保存下来包括删除的文件)→生命周期的设置(在桶设定的下面)→只保留了最后版本的文件→点击更新桶
  4. 点击左侧的菜单栏→应用密钥→点击添加新的应用程序秘钥→请保存好keyIDapplicationKey
  5. 登录CF,左侧主菜单选择计算(Workers)→Workers和Pages→创建→开始使用Workers→从 Hello World! 开始→开始使用→Worker 名称(可以随便填写用于区分项目)→部署
  6. 部署成功后点击编辑代码,将里面的代码删除干净,然后放入以下代码
  7. var __defProp = Object.defineProperty;
    var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
    var encoder = new TextEncoder();
    var HOST_SERVICES = {
      appstream2: "appstream",
      cloudhsmv2: "cloudhsm",
      email: "ses",
      marketplace: "aws-marketplace",
      mobile: "AWSMobileHubService",
      pinpoint: "mobiletargeting",
      queue: "sqs",
      "git-codecommit": "codecommit",
      "mturk-requester-sandbox": "mturk-requester",
      "personalize-runtime": "personalize"
    };
    var UNSIGNABLE_HEADERS = /* @__PURE__ */ new Set([
      "authorization",
      "content-type",
      "content-length",
      "user-agent",
      "presigned-expires",
      "expect",
      "x-amzn-trace-id",
      "range",
      "connection"
    ]);
    var AwsClient = class {
      static {
        __name(this, "AwsClient");
      }
      constructor({ accessKeyId, secretAccessKey, sessionToken, service, region, cache, retries, initRetryMs }) {
        if (accessKeyId == null) throw new TypeError("accessKeyId is a required option");
        if (secretAccessKey == null) throw new TypeError("secretAccessKey is a required option");
        this.accessKeyId = accessKeyId;
        this.secretAccessKey = secretAccessKey;
        this.sessionToken = sessionToken;
        this.service = service;
        this.region = region;
        this.cache = cache || /* @__PURE__ */ new Map();
        this.retries = retries != null ? retries : 10;
        this.initRetryMs = initRetryMs || 50;
      }
      async sign(input, init) {
        if (input instanceof Request) {
          const { method, url, headers, body } = input;
          init = Object.assign({ method, url, headers }, init);
          if (init.body == null && headers.has("Content-Type")) {
            init.body = body != null && headers.has("X-Amz-Content-Sha256") ? body : await input.clone().arrayBuffer();
          }
          input = url;
        }
        const signer = new AwsV4Signer(Object.assign({ url: input.toString() }, init, this, init && init.aws));
        const signed = Object.assign({}, init, await signer.sign());
        delete signed.aws;
        try {
          return new Request(signed.url.toString(), signed);
        } catch (e) {
          if (e instanceof TypeError) {
            return new Request(signed.url.toString(), Object.assign({ duplex: "half" }, signed));
          }
          throw e;
        }
      }
      async fetch(input, init) {
        for (let i = 0; i <= this.retries; i++) {
          const fetched = fetch(await this.sign(input, init));
          if (i === this.retries) {
            return fetched;
          }
          const res = await fetched;
          if (res.status < 500 && res.status !== 429) {
            return res;
          }
          await new Promise((resolve) => setTimeout(resolve, Math.random() * this.initRetryMs * Math.pow(2, i)));
        }
        throw new Error("An unknown error occurred, ensure retries is not negative");
      }
    };
    var AwsV4Signer = class {
      static {
        __name(this, "AwsV4Signer");
      }
      constructor({ method, url, headers, body, accessKeyId, secretAccessKey, sessionToken, service, region, cache, datetime, signQuery, appendSessionToken, allHeaders, singleEncode }) {
        if (url == null) throw new TypeError("url is a required option");
        if (accessKeyId == null) throw new TypeError("accessKeyId is a required option");
        if (secretAccessKey == null) throw new TypeError("secretAccessKey is a required option");
        this.method = method || (body ? "POST" : "GET");
        this.url = new URL(url);
        this.headers = new Headers(headers || {});
        this.body = body;
        this.accessKeyId = accessKeyId;
        this.secretAccessKey = secretAccessKey;
        this.sessionToken = sessionToken;
        let guessedService, guessedRegion;
        if (!service || !region) {
          [guessedService, guessedRegion] = guessServiceRegion(this.url, this.headers);
        }
        this.service = service || guessedService || "";
        this.region = region || guessedRegion || "us-east-1";
        this.cache = cache || /* @__PURE__ */ new Map();
        this.datetime = datetime || (/* @__PURE__ */ new Date()).toISOString().replace(/[:-]|\.\d{3}/g, "");
        this.signQuery = signQuery;
        this.appendSessionToken = appendSessionToken || this.service === "iotdevicegateway";
        this.headers.delete("Host");
        if (this.service === "s3" && !this.signQuery && !this.headers.has("X-Amz-Content-Sha256")) {
          this.headers.set("X-Amz-Content-Sha256", "UNSIGNED-PAYLOAD");
        }
        const params = this.signQuery ? this.url.searchParams : this.headers;
        params.set("X-Amz-Date", this.datetime);
        if (this.sessionToken && !this.appendSessionToken) {
          params.set("X-Amz-Security-Token", this.sessionToken);
        }
        this.signableHeaders = ["host", ...this.headers.keys()].filter((header) => allHeaders || !UNSIGNABLE_HEADERS.has(header)).sort();
        this.signedHeaders = this.signableHeaders.join(";");
        this.canonicalHeaders = this.signableHeaders.map((header) => header + ":" + (header === "host" ? this.url.host : (this.headers.get(header) || "").replace(/\s+/g, " "))).join("\n");
        this.credentialString = [this.datetime.slice(0, 8), this.region, this.service, "aws4_request"].join("/");
        if (this.signQuery) {
          if (this.service === "s3" && !params.has("X-Amz-Expires")) {
            params.set("X-Amz-Expires", "86400");
          }
          params.set("X-Amz-Algorithm", "AWS4-HMAC-SHA256");
          params.set("X-Amz-Credential", this.accessKeyId + "/" + this.credentialString);
          params.set("X-Amz-SignedHeaders", this.signedHeaders);
        }
        if (this.service === "s3") {
          try {
            this.encodedPath = decodeURIComponent(this.url.pathname.replace(/\+/g, " "));
          } catch (e) {
            this.encodedPath = this.url.pathname;
          }
        } else {
          this.encodedPath = this.url.pathname.replace(/\/+/g, "/");
        }
        if (!singleEncode) {
          this.encodedPath = encodeURIComponent(this.encodedPath).replace(/%2F/g, "/");
        }
        this.encodedPath = encodeRfc3986(this.encodedPath);
        const seenKeys = /* @__PURE__ */ new Set();
        this.encodedSearch = [...this.url.searchParams].filter(([k]) => {
          if (!k) return false;
          if (this.service === "s3") {
            if (seenKeys.has(k)) return false;
            seenKeys.add(k);
          }
          return true;
        }).map((pair) => pair.map((p) => encodeRfc3986(encodeURIComponent(p)))).sort(([k1, v1], [k2, v2]) => k1 < k2 ? -1 : k1 > k2 ? 1 : v1 < v2 ? -1 : v1 > v2 ? 1 : 0).map((pair) => pair.join("=")).join("&");
      }
      async sign() {
        if (this.signQuery) {
          this.url.searchParams.set("X-Amz-Signature", await this.signature());
          if (this.sessionToken && this.appendSessionToken) {
            this.url.searchParams.set("X-Amz-Security-Token", this.sessionToken);
          }
        } else {
          this.headers.set("Authorization", await this.authHeader());
        }
        return {
          method: this.method,
          url: this.url,
          headers: this.headers,
          body: this.body
        };
      }
      async authHeader() {
        return [
          "AWS4-HMAC-SHA256 Credential=" + this.accessKeyId + "/" + this.credentialString,
          "SignedHeaders=" + this.signedHeaders,
          "Signature=" + await this.signature()
        ].join(", ");
      }
      async signature() {
        const date = this.datetime.slice(0, 8);
        const cacheKey = [this.secretAccessKey, date, this.region, this.service].join();
        let kCredentials = this.cache.get(cacheKey);
        if (!kCredentials) {
          const kDate = await hmac("AWS4" + this.secretAccessKey, date);
          const kRegion = await hmac(kDate, this.region);
          const kService = await hmac(kRegion, this.service);
          kCredentials = await hmac(kService, "aws4_request");
          this.cache.set(cacheKey, kCredentials);
        }
        return buf2hex(await hmac(kCredentials, await this.stringToSign()));
      }
      async stringToSign() {
        return [
          "AWS4-HMAC-SHA256",
          this.datetime,
          this.credentialString,
          buf2hex(await hash(await this.canonicalString()))
        ].join("\n");
      }
      async canonicalString() {
        return [
          this.method.toUpperCase(),
          this.encodedPath,
          this.encodedSearch,
          this.canonicalHeaders + "\n",
          this.signedHeaders,
          await this.hexBodyHash()
        ].join("\n");
      }
      async hexBodyHash() {
        let hashHeader = this.headers.get("X-Amz-Content-Sha256") || (this.service === "s3" && this.signQuery ? "UNSIGNED-PAYLOAD" : null);
        if (hashHeader == null) {
          if (this.body && typeof this.body !== "string" && !("byteLength" in this.body)) {
            throw new Error("body must be a string, ArrayBuffer or ArrayBufferView, unless you include the X-Amz-Content-Sha256 header");
          }
          hashHeader = buf2hex(await hash(this.body || ""));
        }
        return hashHeader;
      }
    };
    async function hmac(key, string) {
      const cryptoKey = await crypto.subtle.importKey(
        "raw",
        typeof key === "string" ? encoder.encode(key) : key,
        { name: "HMAC", hash: { name: "SHA-256" } },
        false,
        ["sign"]
      );
      return crypto.subtle.sign("HMAC", cryptoKey, encoder.encode(string));
    }
    __name(hmac, "hmac");
    async function hash(content) {
      return crypto.subtle.digest("SHA-256", typeof content === "string" ? encoder.encode(content) : content);
    }
    __name(hash, "hash");
    var HEX_CHARS = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"];
    function buf2hex(arrayBuffer) {
      const buffer = new Uint8Array(arrayBuffer);
      let out = "";
      for (let idx = 0; idx < buffer.length; idx++) {
        const n = buffer[idx];
        out += HEX_CHARS[n >>> 4 & 15];
        out += HEX_CHARS[n & 15];
      }
      return out;
    }
    __name(buf2hex, "buf2hex");
    function encodeRfc3986(urlEncodedStr) {
      return urlEncodedStr.replace(/[!'()*]/g, (c) => "%" + c.charCodeAt(0).toString(16).toUpperCase());
    }
    __name(encodeRfc3986, "encodeRfc3986");
    function guessServiceRegion(url, headers) {
      const { hostname, pathname } = url;
      if (hostname.endsWith(".on.aws")) {
        const match2 = hostname.match(/^[^.]{1,63}\.lambda-url\.([^.]{1,63})\.on\.aws$/);
        return match2 != null ? ["lambda", match2[1] || ""] : ["", ""];
      }
      if (hostname.endsWith(".r2.cloudflarestorage.com")) {
        return ["s3", "auto"];
      }
      if (hostname.endsWith(".backblazeb2.com")) {
        const match2 = hostname.match(/^(?:[^.]{1,63}\.)?s3\.([^.]{1,63})\.backblazeb2\.com$/);
        return match2 != null ? ["s3", match2[1] || ""] : ["", ""];
      }
      const match = hostname.replace("dualstack.", "").match(/([^.]{1,63})\.(?:([^.]{0,63})\.)?amazonaws\.com(?:\.cn)?$/);
      let service = match && match[1] || "";
      let region = match && match[2];
      if (region === "us-gov") {
        region = "us-gov-west-1";
      } else if (region === "s3" || region === "s3-accelerate") {
        region = "us-east-1";
        service = "s3";
      } else if (service === "iot") {
        if (hostname.startsWith("iot.")) {
          service = "execute-api";
        } else if (hostname.startsWith("data.jobs.iot.")) {
          service = "iot-jobs-data";
        } else {
          service = pathname === "/mqtt" ? "iotdevicegateway" : "iotdata";
        }
      } else if (service === "autoscaling") {
        const targetPrefix = (headers.get("X-Amz-Target") || "").split(".")[0];
        if (targetPrefix === "AnyScaleFrontendService") {
          service = "application-autoscaling";
        } else if (targetPrefix === "AnyScaleScalingPlannerFrontendService") {
          service = "autoscaling-plans";
        }
      } else if (region == null && service.startsWith("s3-")) {
        region = service.slice(3).replace(/^fips-|^external-1/, "");
        service = "s3";
      } else if (service.endsWith("-fips")) {
        service = service.slice(0, -5);
      } else if (region && /-\d$/.test(service) && !/-\d$/.test(region)) {
        [service, region] = [region, service];
      }
      return [HOST_SERVICES[service] || service, region || ""];
    }
    __name(guessServiceRegion, "guessServiceRegion");
    // index.js
    var UNSIGNABLE_HEADERS2 = [
      "x-forwarded-proto",
      "x-real-ip",
      "accept-encoding",
      "if-match",
      "if-modified-since",
      "if-none-match",
      "if-range",
      "if-unmodified-since"
    ];
    var HTTPS_PROTOCOL = "https:";
    var HTTPS_PORT = "443";
    function filterHeaders(headers, env) {
      return new Headers(
        Array.from(headers.entries()).filter((pair) => !(UNSIGNABLE_HEADERS2.includes(pair[0]) || pair[0].startsWith("cf-") || "ALLOWED_HEADERS" in env && !env["ALLOWED_HEADERS"].includes(pair[0])))
      );
    }
    __name(filterHeaders, "filterHeaders");
    function createHeadResponse(response) {
      return new Response(null, {
        headers: response.headers,
        status: response.status,
        statusText: response.statusText
      });
    }
    __name(createHeadResponse, "createHeadResponse");
    var index_default = {
      async fetch(request, env) {
        if (!["GET", "HEAD"].includes(request.method)) {
          return new Response(null, {
            status: 405,
            statusText: "Method Not Allowed"
          });
        }
        const url = new URL(request.url);
        url.protocol = HTTPS_PROTOCOL;
        url.port = HTTPS_PORT;
        let path = url.pathname.replace(/^\//, "").replace(/\/$/, "");
        const isRootRequest = path === "";
        if (isRootRequest || url.pathname.startsWith("/api/")) {
          const objectPath = isRootRequest ? "" : url.pathname.replace(/^\/api\//, "");
          url.pathname = objectPath;
          url.hostname = `${env["BUCKET_NAME"]}.${env["B2_ENDPOINT"]}`;
          const headers = filterHeaders(request.headers, env);
          const client = new AwsClient({
            "accessKeyId": env["B2_APPLICATION_KEY_ID"],
            "secretAccessKey": env["B2_APPLICATION_KEY"],
            "service": "s3"
          });
          const signedRequest = await client.sign(url.toString(), {
            method: request.method,
            headers
          });
          const response = await fetch(signedRequest);
          if (request.method === "HEAD") {
            return createHeadResponse(response);
          }
          return response;
        }
        return new Response("Not Found", { status: 404 });
      }
    };
    export {
      index_default as default
    };
  8. 点击部署→部署完成后→在设置-变量与机密里设置变量名,依次填入刚刚创建的只读密钥Key、存储桶名称,存储桶访问Endpoint是一个域名,在你的桶信息里(keyID和key不要弄反了)
  9. 纯文本 ALLOW_LIST_BUCKET true
    纯文本 B2_APPLICATION_KEY 你的Key
    纯文本 B2_APPLICATION_KEY_ID 你的KeyID
    纯文本 B2_ENDPOINT 你的访问ENDPOINT
    纯文本 BUCKET_NAME 存储桶名称
  10. 全部添加好后你可以打开Workers自带的域名后面加上/api/,例如:
    cloudflare-b2.abab.workers.dev/api/
    如果出现了如下内容就说明成功了!
  11. 设置自定义域→CF主页,左侧主菜单选择计算(Workers)→Workers和Pages→选择你刚刚创建的项目→设置→点击(域和路由)→添加→输入自定义域→点击添加域即可
  12. 如果不想要后面的/api/请进行如下操作→CF主页,左侧主菜单选择计算(Workers)→Workers和Pages→选择你刚刚创建的项目→部署→点击右上角的编辑代码进入代码页面→键盘CTRL+F搜索api
  13.     if (isRootRequest || url.pathname.startsWith("/api/")) {
          const objectPath = isRootRequest ? "" : url.pathname.replace(/^\/api\//, "");
  14. 把上面的代码换成下面的
  15.     if (isRootRequest || url.pathname.startsWith("")) {
          const objectPath = isRootRequest ? "" : url.pathname.replace(/^\/\//, "");
  16. 也可以直接删掉api或者换成自己喜欢的后缀(注意斜杠)
  17. 大功告成!!!

给TA打赏
共{{data.count}}人
人已打赏
未分类技术

WP-Zibi主题增加删除文章和论坛帖子时删除引用的附件

2025-9-20 10:36:31

未分类技术

WP多站点使用Redis串站问题解决办法

2025-9-20 13:56:12