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

最后

相关推荐
科技探秘人4 分钟前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
科技探秘人4 分钟前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome
JerryXZR9 分钟前
前端开发中ES6的技术细节二
前端·javascript·es6
七星静香11 分钟前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
q24985969314 分钟前
前端预览word、excel、ppt
前端·word·excel
小华同学ai20 分钟前
wflow-web:开源啦 ,高仿钉钉、飞书、企业微信的审批流程设计器,轻松打造属于你的工作流设计器
前端·钉钉·飞书
problc24 分钟前
Flutter中文字体设置指南:打造个性化的应用体验
android·javascript·flutter
Gavin_91528 分钟前
【JavaScript】模块化开发
前端·javascript·vue.js
懒大王爱吃狼2 小时前
Python教程:python枚举类定义和使用
开发语言·前端·javascript·python·python基础·python编程·python书籍
待磨的钝刨3 小时前
【格式化查看JSON文件】coco的json文件内容都在一行如何按照json格式查看
开发语言·javascript·json