上周日突然感染流感,发烧一天多,头昏脑涨两三天,只好请假休养。不知道是不是二阳,懒得测。工作进展不多,周末适当加加班,博客适当划划水,这周就记录一下 CloudFlare R2 上传文件吧,实测效果很好,简单省事好用,推荐大家使用。
CloudFlare R2 兼容 AWS S3 API,但是不需要那么复杂的 IAM 体系,而且直接对接 CloudFlare CDN,我觉得更好用。跟国内的众多云存储比起来,它不需要备案,还提供域名和证书,用起来更简单。所以,如果你的网站或者服务需要一个云存储,但是暂时不想负担太高的成本,我建议先试试 CF R2。
我假设读者已经拥有 CF 账号,在 R2 里创建了 bucket,并且完成了需要的配置(好像只需要配置一个域名)。接下来,我们需要在自己的业务代码里实现上传。
首先,我们要安装 AWS SDK。
pnpm i @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
接下来,我们需要在项目中建立 S3 配置,其中关键信息都写在 .env
里。
import { S3Client } from '@aws-sdk/client-s3';
const S3 = new S3Client({
region: 'auto',
endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: process.env.R2_ACCESS_KEY,
secretAccessKey: process.env.R2_SECRET_KEY,
},
});
export { S3 };
然后就可以生成预签字的上传路径,相当于提前校验用户身份,这一步需要放在服务器端进行,避免泄漏关键信息。我这里因为业务需求比较简单,没有做复杂的鉴权和检查,如果有需要,就多验证几步。
import { PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import slugify from 'slugify';
import { H3Event } from 'h3';
import { S3 } from '~/lib/s3';
import { ApiResponse, PreSignedUrl } from '~/types';
export default defineEventHandler(async function (event: H3Event): Promise<ApiResponse<PreSignedUrl>> {
// 这一步提交的是预处理信息,没有文件本身,所以还是 json
const json = await readBody(event) as { fileName: string | undefined, fileType: string | undefined };
const { fileName, fileType } = json;
// 生成存储对象 key,其实就是文件名
const objectKey = `${slugify(Date.now().toString())}-${slugify(fileName)}`;
// 生成预签名 url
const preSignedUrl = await getSignedUrl(S3, new PutObjectCommand({
Bucket: process.env.PUBLIC_S3_BUCKET_NAME,
Key: objectKey,
ContentType: fileType,
ACL: 'public-read',
}), {
// 此 URL 5分钟内有效
expiresIn: 60 * 5, // 5 minutes
});
return {
code: 0,
data: {
preSignedUrl,
objectKey,
},
};
});
最后,我们就可以上传文件了。
const response = await $fetch<ApiResponse<PreSignedUrl>>('/api/get-upload-url', {
method: 'POST',
body: {
fileName: file.name,
fileType: file.type,
},
});
const { preSignedUrl, objectKey } = response.data as PreSignedUrl;
const uploadToR2Response = await fetch(preSignedUrl, {
method: 'PUT',
headers: {
'Content-Type': file.type,
},
body: file,
});
if (!uploadToR2Response.ok) {
console.error('Failed to upload file to R2');
}
整个上传过程比较简单,服务器只需要做一些权限校验,剩下来的过程都可以交给 R2。关键是,这样一来,因为不涉及文件操作,就可以放心交给 Serverless 来做,Vercel 友好。上传后的文件自动进入 CF CDN,也很容易使用。
学会使用 R2 之后没多久,我的 Vercel Blob 存储的申请也通过了。必须得说,这些互联网基础设施对我们全栈开发者来说非常重要,而无障碍的域名对目前在国内还是一种奢望,所以用国外服务就成为一种必然。希望未来会更好吧。
欢迎吐槽,共同进步