Bknd存储配置技能Skill bknd-storage-config

此技能用于配置Bknd框架的存储后端,专门处理文件上传管理。支持多种存储适配器如S3、Cloudinary、本地文件系统,涵盖环境变量设置、生产部署和故障排除。关键词:存储配置、Bknd、文件上传、后端开发、S3、Cloudinary、存储适配器、环境变量。

后端开发 0 次安装 0 次浏览 更新于 3/8/2026

name: bknd-storage-config description: 用于配置文件上传的存储后端。涵盖 S3 兼容存储(AWS S3、Cloudflare R2、DigitalOcean Spaces)、Cloudinary 媒体存储、用于开发的本地文件系统适配器、适配器配置选项、环境变量和生产存储设置。

存储配置

配置 Bknd 媒体模块的存储后端。

先决条件

  • 已安装 bknd
  • 对于 S3:已创建具有适当权限的存储桶
  • 对于 Cloudinary:具有 API 凭据的账户
  • 对于 R2:具有 R2 绑定的 Cloudflare Workers 环境

何时使用 UI 模式

  • 管理面板不支持存储配置
  • 必须在代码中配置存储

何时使用代码模式

  • 所有存储配置(必需)
  • 为不同环境设置不同的适配器
  • 使用凭据的生产存储

存储适配器概述

适配器 类型 使用案例
s3 S3 兼容 AWS S3、Cloudflare R2(外部)、DigitalOcean Spaces、MinIO
cloudinary 媒体优化 图像/视频转换、CDN 交付
local 文件系统 仅用于开发(Node.js/Bun 运行时)
r2 Cloudflare R2 具有 R2 绑定的 Cloudflare Workers

逐步指南:S3 适配器

步骤 1:创建 S3 存储桶

在 AWS 控制台或通过 CLI 创建存储桶:

aws s3 mb s3://my-app-uploads --region us-east-1

步骤 2:配置 CORS(如果浏览器上传)

{
  "CORSRules": [{
    "AllowedOrigins": ["https://yourapp.com"],
    "AllowedMethods": ["GET", "PUT", "POST", "DELETE"],
    "AllowedHeaders": ["*"],
    "ExposeHeaders": ["ETag"]
  }]
}

步骤 3:获取访问凭据

创建具有 S3 访问权限的 IAM 用户并获取:

  • 访问密钥 ID
  • 秘密访问密钥

步骤 4:配置 Bknd

import { defineConfig } from "bknd";

export default defineConfig({
  media: {
    enabled: true,
    adapter: {
      type: "s3",
      config: {
        access_key: process.env.S3_ACCESS_KEY,
        secret_access_key: process.env.S3_SECRET_KEY,
        url: "https://my-bucket.s3.us-east-1.amazonaws.com",
      },
    },
  },
});

步骤 5:添加环境变量

# .env
S3_ACCESS_KEY=AKIA...
S3_SECRET_KEY=wJalr...

S3 URL 格式

不同的 S3 兼容服务使用不同的 URL 格式:

// AWS S3
url: "https://{bucket}.s3.{region}.amazonaws.com"
// 示例:"https://my-bucket.s3.us-east-1.amazonaws.com"

// Cloudflare R2(通过 S3 API 外部访问)
url: "https://{account_id}.r2.cloudflarestorage.com/{bucket}"
// 示例:"https://abc123.r2.cloudflarestorage.com/my-bucket"

// DigitalOcean Spaces
url: "https://{bucket}.{region}.digitaloceanspaces.com"
// 示例:"https://my-bucket.nyc3.digitaloceanspaces.com"

// MinIO(自托管)
url: "http://localhost:9000/{bucket}"

逐步指南:Cloudinary 适配器

步骤 1:获取 Cloudinary 凭据

从 Cloudinary 仪表板复制:

  • 云名称
  • API 密钥
  • API 秘密

步骤 2:配置 Bknd

import { defineConfig } from "bknd";

export default defineConfig({
  media: {
    enabled: true,
    adapter: {
      type: "cloudinary",
      config: {
        cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
        api_key: process.env.CLOUDINARY_API_KEY,
        api_secret: process.env.CLOUDINARY_API_SECRET,
      },
    },
  },
});

步骤 3:添加环境变量

# .env
CLOUDINARY_CLOUD_NAME=my-cloud
CLOUDINARY_API_KEY=123456789
CLOUDINARY_API_SECRET=abcdef...

可选:上传预设

用于无符号上传或自定义转换:

adapter: {
  type: "cloudinary",
  config: {
    cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
    api_key: process.env.CLOUDINARY_API_KEY,
    api_secret: process.env.CLOUDINARY_API_SECRET,
    upload_preset: "my-preset",  // 可选
  },
},

逐步指南:本地适配器(开发)

步骤 1:创建上传目录

mkdir -p ./uploads

步骤 2:注册和配置

import { defineConfig } from "bknd";
import { registerLocalMediaAdapter } from "bknd/adapter/node";

// 注册本地适配器
const local = registerLocalMediaAdapter();

export default defineConfig({
  media: {
    enabled: true,
    adapter: local({ path: "./uploads" }),
  },
});

文件通过 /api/media/file/{filename} 提供服务。

运行时注意事项

本地适配器需要 Node.js 或 Bun 运行时(文件系统访问)。在以下环境中无法工作:

  • Cloudflare Workers
  • Vercel Edge Functions
  • 浏览器环境

逐步指南:Cloudflare R2(Workers)

步骤 1:创建 R2 存储桶

wrangler r2 bucket create my-bucket

步骤 2:在 wrangler.toml 中添加 R2 绑定

[[r2_buckets]]
binding = "MY_BUCKET"
bucket_name = "my-bucket"

步骤 3:配置 Bknd

import { serve, type CloudflareBkndConfig } from "bknd/adapter/cloudflare";

const config: CloudflareBkndConfig = {
  app: (env) => ({
    connection: { url: env.DB },
    config: {
      media: {
        enabled: true,
        adapter: {
          type: "r2",
          config: {
            binding: "MY_BUCKET",
          },
        },
      },
    },
  }),
};

export default serve(config);

R2 适配器直接使用 Cloudflare Workers 绑定,无需外部凭据。

媒体模块选项

大小限制

export default defineConfig({
  media: {
    enabled: true,
    body_max_size: 10 * 1024 * 1024,  // 最大上传 10MB
    adapter: { ... },
  },
});

默认大小行为

如果未设置 body_max_size,上传无大小限制。在生产中始终设置合理的限制。

基于环境的配置

为开发和生产使用不同的适配器:

import { defineConfig } from "bknd";
import { registerLocalMediaAdapter } from "bknd/adapter/node";

const local = registerLocalMediaAdapter();
const isDev = process.env.NODE_ENV !== "production";

export default defineConfig({
  media: {
    enabled: true,
    body_max_size: 25 * 1024 * 1024,  // 25MB
    adapter: isDev
      ? local({ path: "./uploads" })
      : {
          type: "s3",
          config: {
            access_key: process.env.S3_ACCESS_KEY,
            secret_access_key: process.env.S3_SECRET_KEY,
            url: process.env.S3_BUCKET_URL,
          },
        },
  },
});

验证存储配置

检查媒体模块是否启用

import { Api } from "bknd";

const api = new Api({ host: "http://localhost:7654" });

// 列出文件(如果还没有上传,则为空)
const { ok, data, error } = await api.media.listFiles();

if (ok) {
  console.log("媒体模块正常工作,文件数:", data.length);
} else {
  console.error("媒体错误:", error);
}

测试上传

async function testStorage() {
  const testFile = new File(["测试内容"], "test.txt", {
    type: "text/plain"
  });

  const { ok, data, error } = await api.media.upload(testFile);

  if (ok) {
    console.log("上传成功:", data.name);

    // 清理
    await api.media.deleteFile(data.name);
    console.log("清理完成");
  } else {
    console.error("上传失败:", error);
  }
}

通过 REST 检查

# 列出文件
curl http://localhost:7654/api/media/files

# 上传测试文件
echo "测试" | curl -X POST \
  -H "Content-Type: text/plain" \
  --data-binary @- \
  http://localhost:7654/api/media/upload/test.txt

完整配置示例

AWS S3 生产环境

import { defineConfig } from "bknd";

export default defineConfig({
  connection: {
    url: process.env.DATABASE_URL,
  },
  config: {
    media: {
      enabled: true,
      body_max_size: 50 * 1024 * 1024,  // 50MB
      adapter: {
        type: "s3",
        config: {
          access_key: process.env.AWS_ACCESS_KEY_ID,
          secret_access_key: process.env.AWS_SECRET_ACCESS_KEY,
          url: `https://${process.env.S3_BUCKET}.s3.${process.env.AWS_REGION}.amazonaws.com`,
        },
      },
    },
  },
});

Cloudflare R2 + D1

import { serve, type CloudflareBkndConfig } from "bknd/adapter/cloudflare";

const config: CloudflareBkndConfig = {
  app: (env) => ({
    connection: { url: env.DB },  // D1 绑定
    config: {
      media: {
        enabled: true,
        body_max_size: 25 * 1024 * 1024,
        adapter: {
          type: "r2",
          config: { binding: "UPLOADS" },
        },
      },
    },
  }),
};

export default serve(config);

开发环境热重载

import { defineConfig } from "bknd";
import { registerLocalMediaAdapter } from "bknd/adapter/node";

const local = registerLocalMediaAdapter();

export default defineConfig({
  connection: {
    url: "file:data.db",
  },
  config: {
    media: {
      enabled: true,
      adapter: local({ path: "./public/uploads" }),
    },
  },
});

常见陷阱

S3 403 禁止访问

问题: 上传失败并返回 403 错误。

原因:

  1. 无效凭据
  2. 存储桶策略阻止访问
  3. URL 格式不正确

修复:

// 检查 URL 格式 - 不能有尾部斜杠
url: "https://bucket.s3.region.amazonaws.com"  // 正确
url: "https://bucket.s3.region.amazonaws.com/" // 错误

// 验证凭据
console.log("密钥:", process.env.S3_ACCESS_KEY?.substring(0, 8) + "...");

本地适配器 404

问题: 上传后文件找不到。

原因:

  1. 上传目录不存在
  2. 路径错误(绝对 vs 相对)

修复:

# 先创建目录
mkdir -p ./uploads

# 使用项目根目录的相对路径
adapter: local({ path: "./uploads" })  // 正确
adapter: local({ path: "/uploads" })   // 错误(绝对)

R2 绑定未找到

问题: 错误“未找到具有键的 R2Bucket”。

修复: 确保 wrangler.toml 有正确的绑定:

[[r2_buckets]]
binding = "MY_BUCKET"      // 此名称用于配置
bucket_name = "actual-bucket"

Cloudinary 上传静默失败

问题: putObject 返回未定义。

原因:

  1. 无效凭据
  2. API 密钥没有上传权限

修复:

  • 在 Cloudinary 仪表板验证凭据
  • 确保 API 密钥具有写权限(非只读)

运行时适配器错误

问题: 本地适配器在无服务器环境中失败。

修复: 为无服务器环境使用 S3/R2/Cloudinary:

// Cloudflare Workers - 使用 r2
// Vercel - 使用 s3
// AWS Lambda - 使用 s3
// Node.js 服务器 - 本地适配器可用于开发

缺少环境变量

问题: 运行时凭据未定义。

修复:

// 添加验证
if (!process.env.S3_ACCESS_KEY) {
  throw new Error("未设置 S3_ACCESS_KEY");
}

// 或为开发提供默认值
const config = {
  access_key: process.env.S3_ACCESS_KEY ?? "dev-key",
  // ...
};

CORS 未配置

问题: 浏览器上传被阻止。

修复: 在存储桶本身(AWS/R2 控制台)配置 CORS,而不是在 Bknd 中。

安全检查清单

  1. 从不提交凭据 - 使用环境变量
  2. 限制存储桶访问 - 除非需要,否则不要使存储桶公开
  3. 设置大小限制 - 始终配置 body_max_size
  4. 使用 HTTPS - 特别是对于基于凭据的上传
  5. 轮换密钥 - 定期轮换访问密钥
  6. 最小权限原则 - IAM 用户只需对一个存储桶具有 S3 访问权限

注意事项

要做:

  • 使用环境变量处理凭据
  • 在生产中设置 body_max_size
  • 为生产使用 S3/R2/Cloudinary
  • 为浏览器上传配置 CORS
  • 部署前测试存储

不要做:

  • 将凭据提交到 git
  • 在生产中使用本地适配器
  • 让上传大小无限制
  • 忘记为本地适配器创建上传目录
  • 在 S3 URL 中使用尾部斜杠

相关技能

  • bknd-file-upload - 配置存储后上传文件
  • bknd-serve-files - 服务和交付上传的文件
  • bknd-env-config - 管理环境变量
  • bknd-deploy-hosting - 部署生产存储
  • bknd-assign-permissions - 设置媒体权限