如何将 Cloudflare 的 R2 存储桶与下一个 JS 13 api 一起使用?

Moh*_*ele 1 cloudflare reactjs next.js cloudflare-workers

我最近遇到了 Cloudflare 的 R2 存储桶。这个桶有大量的免费套餐,我想将其用于我的个人项目。但没有关于如何在下一个 JS 13 中使用它的文档。可能有一些教程展示如何在 Node js 中使用它。

那么我如何使用 Next 13 API 路由上传到 R2 存储?

dea*_*904 5

从我的 reddit 评论中复制这个解决方案。我选择了选项 2:直接从浏览器上传到 AWS S3

\n

不敢相信没有人写过有关使用 cloudflare r2 与 next 13 目录的指南app/,所以我只是做了 \xe2\x86\x93

\n

cloudflare cors 政策

\n
[\n    {\n        "AllowedOrigins": [\n            "http://localhost:3000"\n        ],\n        "AllowedMethods": [\n            "GET",\n            "PUT",\n            "POST",\n            "HEAD",\n            "DELETE"\n        ],\n        "AllowedHeaders": [\n            "*"\n        ],\n        "ExposeHeaders": [],\n        "MaxAgeSeconds": 3000\n    }\n]\n
Run Code Online (Sandbox Code Playgroud)\n

安装这些依赖项

\n

npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner

\n

.env.local

\n
\n

从 cloudflare r2 仪表板生成令牌。在 Cloudflare Workers YouTube 频道上观看官方 YouTube 视频

\n
\n
R2_ACCESS_KEY_ID=xxxx\nR2_SECRET_ACCESS_KEY=xxxx\nR2_BUCKET_NAME=xxxx\nR2_ACCOUNT_ID=xxxx\n
Run Code Online (Sandbox Code Playgroud)\n

库/r2.ts

\n
import { S3Client } from \'@aws-sdk/client-s3\'\n\nexport const r2 = new S3Client({\n    region: \'auto\',\n    endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,\n    credentials: {\n        accessKeyId: process.env.R2_ACCESS_KEY_ID || \'\',\n        secretAccessKey: process.env.R2_SECRET_ACCESS_KEY || \'\',\n    },\n})\n
Run Code Online (Sandbox Code Playgroud)\n

应用程序/(站点)/admin/page.tsx

\n
\'use client\'\nimport React from \'react\'\nimport { DocumentIcon } from \'@heroicons/react/24/solid\'\n\nconst Admin = () => {\n    const [file, setFile] = React.useState<File>()\n\n    const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n        if (event.target.files) {\n            const currentFile = event.target.files[0]\n            setFile(currentFile)\n        }\n    }\n\n    const handleUpload = async () => {\n        if (!file) return\n\n        const formData = new FormData()\n        formData.append(\'file\', file)\n\n        const response = await fetch(\'/api/upload\', {\n            method: \'POST\',\n        })\n        const { url } = await response.json()\n        await fetch(url, {\n            method: \'PUT\',\n            body: formData,\n        })\n    }\n\n    return (\n        <div className="min-h-screen bg-slate-900 text-white space-y-12">\n            <div className="max-w-2xl mx-auto py-24 px-4">\n                <h2 className="text-base font-semibold leading-7 text-white">\n                    Admin Panel\n                </h2>\n                <p className="mt-1 text-sm leading-6 text-gray-400">\n                    Upload the latest version of the pdf file.\n                </p>\n\n                <div className="mt-10 grid grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">\n                    <div className="col-span-full">\n                        <label\n                            htmlFor="pdf-file"\n                            className="block text-sm font-medium leading-6 text-white"\n                        >\n                            PDF\n                        </label>\n                        <div className="mt-2 flex justify-center rounded-lg border border-dashed border-white/25 px-6 py-10">\n                            <div className="text-center">\n                                <DocumentIcon\n                                    className="mx-auto h-12 w-12 text-gray-500"\n                                    aria-hidden="true"\n                                />\n                                <div className="mt-4 text-sm leading-6 text-gray-400">\n                                    <label\n                                        htmlFor="file-upload"\n                                        className="relative cursor-pointer rounded-md bg-gray-900 font-semibold text-white focus-within:outline-none focus-within:ring-2 focus-within:ring-indigo-600 focus-within:ring-offset-2 focus-within:ring-offset-gray-900 hover:text-indigo-500"\n                                    >\n                                        <span>Upload a file</span>\n                                        <input\n                                            type="file"\n                                            accept="application/pdf"\n                                            id="file-upload"\n                                            name="file-upload"\n                                            className="sr-only"\n                                            onChange={handleFileChange}\n                                        />\n                                    </label>\n                                </div>\n                                <p className="text-xs leading-5 text-gray-400">\n                                    {file?.name ? file.name : \'PDF up to 100MB\'}\n                                </p>\n                            </div>\n                        </div>\n                    </div>\n                </div>\n                <div className="mt-6 flex items-center justify-end gap-x-6">\n                    <button\n                        type="submit"\n                        className="rounded-md bg-indigo-500 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-500"\n                        onClick={handleUpload}\n                    >\n                        Upload\n                    </button>\n                </div>\n            </div>\n        </div>\n    )\n}\n\nexport default Admin\n
Run Code Online (Sandbox Code Playgroud)\n

应用程序/api/upload/route.ts

\n
import { NextResponse } from \'next/server\'\nimport chalk from \'chalk\'\nimport { PutObjectCommand } from \'@aws-sdk/client-s3\'\nimport { getSignedUrl } from \'@aws-sdk/s3-request-presigner\'\n\nimport { r2 } from \'@/lib/r2\'\n\nexport async function POST(request: Request) {\n    try {\n        console.log(chalk.yellow(`Generating an upload URL!`))\n\n        const signedUrl = await getSignedUrl(\n            r2,\n            new PutObjectCommand({\n                Bucket: process.env.R2_BUCKET_NAME,\n                Key: `filename.pdf`,\n            }),\n            { expiresIn: 60 }\n        )\n\n        console.log(chalk.green(`Success generating upload URL!`))\n\n        return NextResponse.json({ url: signedUrl })\n    } catch (err) {\n        console.log(\'error\')\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

另外,还实现了下载:

\n

应用程序/api/download/route.ts

\n
import { GetObjectCommand } from \'@aws-sdk/client-s3\'\nimport chalk from \'chalk\'\n\nimport { r2 } from \'@/lib/r2\'\n\nexport async function GET() {\n    try {\n        console.log(chalk.yellow(`Retrieving pdf from R2!`))\n\n        const pdf = await r2.send(\n            new GetObjectCommand({\n                Bucket: process.env.R2_BUCKET_NAME,\n                Key: \'filename.pdf\',\n            })\n        )\n\n        if (!pdf) {\n            throw new Error(\'pdf not found.\')\n        }\n\n        return new Response(pdf.Body?.transformToWebStream(), {\n            headers: {\n                \'Content-Type\': \'application/pdf\',\n            },\n        })\n    } catch (err) {\n        console.log(\'error\', err)\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

应用程序/(站点)/admin/page.tsx

\n
const handleDownload = async () => {\n    const response = await fetch(\'/api/download\')\n    const blob = await response.blob()\n    const fileURL = window.URL.createObjectURL(blob)\n    let anchor = document.createElement(\'a\')\n    anchor.href = fileURL\n    anchor.download = \'filename.pdf\'\n    anchor.click()\n}\n\n// add below upload button\n<button\n    type="button"\n    className="rounded-md bg-pink-500 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-pink-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-pink-500"\n    onClick={handleDownload}\n>\n    Download\n</button>\n
Run Code Online (Sandbox Code Playgroud)\n