如何在 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 获取文件数据流,然后前端通过各种方式把文件流转换并触发下载。

最后

相关推荐
沉默璇年1 小时前
react中useMemo的使用场景
前端·react.js·前端框架
yqcoder1 小时前
reactflow 中 useNodesState 模块作用
开发语言·前端·javascript
2401_882727571 小时前
BY组态-低代码web可视化组件
前端·后端·物联网·低代码·数学建模·前端框架
SoaringHeart2 小时前
Flutter进阶:基于 MLKit 的 OCR 文字识别
前端·flutter
会发光的猪。2 小时前
css使用弹性盒,让每个子元素平均等分父元素的4/1大小
前端·javascript·vue.js
天下代码客2 小时前
【vue】vue中.sync修饰符如何使用--详细代码对比
前端·javascript·vue.js
猫爪笔记2 小时前
前端:HTML (学习笔记)【1】
前端·笔记·学习·html
前端李易安3 小时前
Webpack 热更新(HMR)详解:原理与实现
前端·webpack·node.js
红绿鲤鱼3 小时前
React-自定义Hook与逻辑共享
前端·react.js·前端框架
Domain-zhuo3 小时前
什么是JavaScript原型链?
开发语言·前端·javascript·jvm·ecmascript·原型模式