如何在 Next.js 13的 app/ 目录中使用 Cloudflare R2 存储

解决方案

简直不敢相信居然没有人写过一篇关于如何在 Next.js 13 的 app/ 目录中使用 Cloudflare R2 的指南,所以我就自己写了一篇。

Cloudflare CORS 策略配置

css 复制代码
[  {    "AllowedOrigins": [      "http://localhost:3000"    ],
    "AllowedMethods": [
      "GET", 
      "PUT",
      "POST",   
      "HEAD",
      "DELETE"
    ],
    "AllowedHeaders": [
      "*"
    ],
    "ExposeHeaders": [],
    "MaxAgeSeconds": 3000    
  }
]

所需的依赖

bash 复制代码
npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner

环境变量配置

ini 复制代码
// 在 .env.local 中配置
// 从 Cloudflare R2 控制面板生成 token,可参考官方的 Youtube 视频教程

R2_ACCESS_KEY_ID=xxxx  
R2_SECRET_ACCESS_KEY=xxxx
R2_BUCKET_NAME=xxxx
R2_ACCOUNT_ID=xxxx

R2客户端配置

javascript 复制代码
// lib/r2.ts

import { S3Client } from '@aws-sdk/client-s3'

export const r2 = new S3Client({
  region: 'auto',
  endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
  credentials: {
    accessKeyId: process.env.R2_ACCESS_KEY_ID || '',
    secretAccessKey: process.env.R2_SECRET_ACCESS_KEY || '',
  },
})

上传组件实现

javascript 复制代码
// app/(site)/admin/page.tsx  
'use client'

import React from 'react'
import { DocumentIcon } from '@heroicons/react/24/solid'

const Admin = () => {

  // 文件状态管理
  const [file, setFile] = React.useState<File>()

  // 文件改变事件处理
  const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    if (event.target.files) {
      const currentFile = event.target.files[0]
      setFile(currentFile) 
    }
  }

  // 文件上传逻辑
  const handleUpload = async () => {
    if (!file) return
    
    const formData = new FormData()
    formData.append('file', file)

    // 1. 生成上传URL
    const response = await fetch('/api/upload', {
      method: 'POST',
    })
    const { url } = await response.json()
    
    // 2. 使用上传URL上传文件 
    await fetch(url, {  
      method: 'PUT',
      body: formData,
    })
  }

  return (
    <div className="bg-slate-900 text-white">
      {/* 示例UI */}

      <button onClick={handleUpload}>
        上传
      </button>
    </div>
  )  
}

export default Admin

上传API路由实现

javascript 复制代码
// app/api/upload/route.ts  

import { NextResponse } from 'next/server'
import chalk from 'chalk'
import { PutObjectCommand } from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'

import { r2 } from '@/lib/r2'

export async function POST(request: Request) {

  try {
    
    console.log(chalk.yellow(`正在生成上传URL!`))  

    // 生成上传URL
    const signedUrl = await getSignedUrl(
      r2,
      new PutObjectCommand({
        Bucket: process.env.R2_BUCKET_NAME, 
        Key: `filename.pdf`,    
      }),
      { expiresIn: 60 } 
    )

    console.log(chalk.green(`上传URL生成成功!`))
    
    // 返回上传URL
    return NextResponse.json({ url: signedUrl })

  } catch (err) {
    console.log('出错了')
  } 

}

总的来说,主要就是配置好 R2 客户端,然后生成上传 URL,前端使用该 URL 上传文件。同样的方法也可以实现下载文件。

实现文件下载

javascript 复制代码
// app/api/download/route.ts

import { GetObjectCommand } from '@aws-sdk/client-s3'
import chalk from 'chalk'

import { r2 } from '@/lib/r2'

export async function GET() {
  try {

    console.log(chalk.yellow(`从 R2 检索 PDF文件!`))  

    // 从 R2 获取 PDF 文件
    const pdf = await r2.send(
      new GetObjectCommand({
        Bucket: process.env.R2_BUCKET_NAME,
        Key: 'filename.pdf', 
      })
    )

    if (!pdf) {
      throw new Error('未找到 PDF 文件。')
    }

    // 返回 PDF 文件流
    return new Response(pdf.Body?.transformToWebStream(), {   
      headers: {
        'Content-Type': 'application/pdf',
      },
    })

  } catch (err) {
    console.log('出错:', err) 
  }
}

前端实现下载

javascript 复制代码
// app/(site)/admin/page.tsx

const handleDownload = async () => {

  // 调用下载 API 
  const response = await fetch('/api/download')
  
  // 获取文件 Blob
  const blob = await response.blob()
  
  // 生成临时 URL
  const fileURL = window.URL.createObjectURL(blob) 
  
  // 触发下载
  let anchor = document.createElement('a')
  anchor.href = fileURL
  anchor.download = 'filename.pdf'
  anchor.click()

}

// 添加下载按钮
<button onClick={handleDownload}>
  下载
</button>

主要就是通过 API 从 R2 获取文件数据流,然后前端通过各种方式把文件流转换并触发下载。

最后

相关推荐
邪恶的贝利亚2 分钟前
定时器设计
java·linux·前端
inksci22 分钟前
Vue 3 打开 el-dialog 时使 el-input 获取焦点
前端·javascript·vue.js
若愚67921 小时前
前端取经路——量子UI:响应式交互新范式
前端·ui·交互
PHASELESS4111 小时前
HTML常用标签用法全解析:构建语义化网页的核心指南
前端·html
粉末的沉淀1 小时前
css:倒影倾斜效果
前端·css
zandy10112 小时前
如何快速入门-衡石科技分析平台
服务器·前端·科技·数据库管理员
邝邝邝邝丹3 小时前
React学习———React Router
前端·学习·react.js
Yvonne爱编码3 小时前
CSS- 2.1 实战之图文混排、表格、表单
前端·css·html·github·状态模式·html5·hbuilder
前端小巷子3 小时前
CSS面试题汇总
前端·css·面试
绝美焦栖3 小时前
vue复杂数据类型多层嵌套的监听
前端·javascript·vue.js