Laf 云存储详解:S3 兼容对象存储、存储桶权限模型、网站托管与 Cloud Bin 机制
【免费下载链接】lafLaf is a vibrant cloud development platform that provides essential tools like cloud functions, databases, and storage solutions. It enables developers to quickly unleash their creativity and bring innovative ideas to life with ease.项目地址: https://gitcode.com/GitHub_Trending/la/laf
Laf 云存储是一个兼容 S3 API 的对象存储服务,为云函数、客户端应用提供文件上传、下载、删除与列表等能力,同时支持将存储桶发布为静态网站。阅读本文后,你将理解存储桶的命名与隔离规则、三种桶权限的适用场景、网站托管的开启方式,以及 Laf 为每个应用自动创建的 Cloud Bin 系统桶的用途,并能结合@lafjs/cloudSDK 与仓库源码验证这些行为的底层实现。
云存储是什么
laf云存储是兼容 S3 API 的对象存储服务,支持多种存储桶权限(公有读、私有读、公有读写),支持通过 HTTP/HTTPS 访问,支持自定义文件元数据,支持文件上传、下载、删除、获取文件列表等操作。
从 SDK 实现看,这些操作并非自造协议:packages/cloud-sdk/src/storage.ts 中的CloudStorage与CloudStorageBucket类直接基于@aws-sdk/client-s3构建,readFile、writeFile、deleteFile、listFiles分别对应 S3 的GetObject、PutObject、DeleteObject、ListObjects命令;上传/下载地址则由@aws-sdk/s3-request-presigner的getSignedUrl生成签名 URL。因此,熟悉 S3 的开发者可以直接迁移到 Laf 云存储上。
CloudStorage内部维护了两类 S3 客户端,分别走不同端点,所需的环境变量在运行时断言中清晰可见:
| 客户端 | 端点环境变量 | 用途 |
|---|---|---|
getExternalS3Client() | OSS_EXTERNAL_ENDPOINT | 通过外网端点访问桶,用于生成对外签名 URL |
getInternalS3Client() | OSS_INTERNAL_ENDPOINT | 通过内网端点读写文件,readFile/writeFile/deleteFile/listFiles均走此客户端 |
两者还共同依赖OSS_REGION、OSS_ACCESS_KEY、OSS_ACCESS_SECRET,并统一设置forcePathStyle: true(以路径风格寻址桶,即endpoint/{bucket}/{key}形式)。
存储桶(Bucket)
在云存储中,存储桶是存储文件的容器,所有的文件都必须存储在存储桶中,每个存储桶都有一个唯一的名称。云存储支持创建多个存储桶,每一个存储桶都是一个独立的存储空间,文件桶的名称必须遵守以下规则:
- 名称必须是全局唯一的;
- 只能包含小写字母、数字和
-字符; - 长度不能超过 63 个字符。
appid 前缀:应用级隔离的关键
在 laf 应用中,文件桶的名称是由应用的appid作为前缀,然后加上存储桶的名称,例如:{appid}-bucket,这样可以保证每个应用的文件桶名称都是唯一的。
这一规则在 SDK 侧有明确的代码实现。cloud.storage.bucket(bucketName)在 packages/cloud-sdk/src/storage.ts 中的逻辑是:
bucket(bucketName: string): CloudStorageBucket { assert(bucketName, 'bucketName is required') if (IS_SEALAF || bucketName.startsWith(`${this.appid}-`)) { return new CloudStorageBucket(this, bucketName) } const name = `${this.appid}-${bucketName}` return new CloudStorageBucket(this, name) }也就是说:调用cloud.storage.bucket('data')时,若当前环境注入了APPID,SDK 会自动将其解析为{appid}-data这个真实桶名;若传入的名字已经带上了{appid}-前缀,则原样使用。appid本身来自云函数运行时的process.env.APPID,缺失时会在断言处直接抛错。
在服务端,server/src/storage/bucket.service.ts 的create方法会先以dto.fullname(appid)拼出完整桶名,写入系统数据库的StorageBucket集合(初始状态为Active/Creating),随后由后台任务在底层对象存储(MinIO)中真正创建该桶——从 server/src/storage/bucket.service.ts 中updateBucketPolicy经由MinioService调用的链路可以推断,Laf 的对象存储后端为 MinIO,这也解释了其 API 与 S3 完全兼容的原因。
存储桶权限
云存储支持多种存储桶权限,在创建存储桶时可以指定桶的权限。文档与源码中的权限对照如下:
| 权限 | 含义 | 读取 | 写入 |
|---|---|---|---|
public(公有读写) | 无需凭证即可读写桶中的文件 | 无需凭证 | 无需凭证 |
readonly(公有读) | 无需凭证即可读,写需要凭证 | 无需凭证 | 需要凭证 |
private(私有读写) | 读写都需要凭证 | 需要凭证 | 需要凭证 |
通常不建议使用
public权限,因为这样会导致存储桶中的文件可以被任何人访问。
服务端对权限的建模见 server/src/storage/entities/storage-bucket.ts:
export enum BucketPolicy { readwrite = 'readwrite', readonly = 'readonly', private = 'private', }从源码结构看,文档中称公有读写权限为public,而在服务端的BucketPolicy枚举中对应的存储值是readwrite,修改权限时(BucketService.updateOne)会同步调用 MinIO 更新桶策略,只有底层返回204才会落库更新,保证了数据库记录与对象存储策略的一致性。
权限与文件访问方式的关系
权限直接决定了获取文件访问地址的方式,这也是 SDK 中 URL 方法的设计依据(见 packages/cloud-sdk/src/storage.ts):
- 桶权限为
public或readonly时,可直接使用公共访问地址,bucket.externalUrl(filename)拼接出{externalEndpoint}/{bucket}/{key}形式的永久地址,永久有效,除非桶权限被修改; - 桶权限为
private时,必须生成带签名的临时访问地址,bucket.getDownloadUrl(filename, expiresIn)默认有效期3600 秒(1 小时),最大可设置为3600 * 24 * 7秒(7 天),客户端拿到 URL 后无需任何凭证即可直接下载。
对应地,上传侧的bucket.getUploadUrl(filename, expiresIn)同样默认 1 小时、最长 7 天,客户端使用返回的 URL 直接PUT上传文件。
云函数中操作文件
以上述权限模型为基础,在云函数中通过@lafjs/cloud操作存储桶的典型用法如下(示例使用data存储桶,使用前需先在控制台创建该桶):
上传文件
import cloud from '@lafjs/cloud' export default async function (ctx: FunctionContext) { // 获取存储桶 const bucket = cloud.storage.bucket('data') // 写文件 const content = 'hello, laf' await bucket.writeFile('laf.html', content) }writeFile的body参数接受string | Blob | Buffer | Uint8Array | Readable流(见 storage.ts 的签名),文件类型默认是application/octet-stream,需要时通过options.ContentType指定:
await bucket.writeFile('index.html', content, { ContentType: 'text/html' })上传本地文件可以用文件流或 Buffer:
import cloud from '@lafjs/cloud' import { createReadStream } from 'node:fs' import { readFile } from 'node:fs/promises' // 方式一:文件流 const stream = createReadStream('./package.json') await bucket.writeFile('laf.json', stream, { ContentType: 'application/json' }) // 方式二:Buffer const buffer = await readFile('./package.json') await bucket.writeFile('laf.json', buffer, { ContentType: 'application/json' })读取、删除与列表
// 读文件并转为字符串 const res = await bucket.readFile('index.html') const data = await res.Body.transformToString() // 读文件为 Buffer const bytes = await res.Body.transformToByteArray() const buffer = Buffer.from(bytes) // 删除文件(注意:即使文件不存在也会正常返回,不会抛出异常) await bucket.deleteFile('index.html')listFiles返回S3 ListObjects风格的结果,res.Contents中每个对象包含Key、LastModified、ETag、Size字段。它支持三类常用参数:
Delimiter: '/':只列当前目录(对象名按分隔符分组);Prefix: 'images/':筛选指定前缀(子目录)下的对象;MaxKeys: 10:限定单次返回数量,默认 1000。
分页依赖返回结果中的IsTruncated与NextMarker:前者表示是否还有下一页,后者作为下一页的Marker参数传入。
// 获取 images/ 子目录下、前 10 个文件 const res = await bucket.listFiles({ Prefix: 'images/', Delimiter: '/', MaxKeys: 10 })网站托管
laf云存储支持将存储桶中的文件作为静态网站进行托管,开启步骤如下:
- 将存储桶的权限设置为
readonly——只有readonly权限的存储桶才能作为静态网站进行托管; - 在 laf 控制台中开启存储桶的网站托管功能;
- 在存储桶中创建一个名为
index.html的文件,作为默认首页。
开启后 Laf 会自动为网站提供一个独立的访问域名,也可以绑定自定义域名。
绑定自定义域名
网站托管支持绑定自己的域名,并会自动为其生成 HTTPS 证书,同时支持http和https访问。按页面提示完成域名的CNAME解析,解析生效(通常 1~10 分钟)后即可继续绑定操作;绑定后 Laf 会自动为域名配置 SSL 证书,该过程约需 1~2 分钟,之后即可通过https访问。
从服务端代码结构看,桶与域名、网站托管的关联关系是显式建模的:BucketService.findOne/findAll通过聚合查询将BucketDomain(自定义域名)与WebsiteHosting集合关联到每个存储桶上(见 server/src/storage/bucket.service.ts),控制台的域名绑定与托管开关正是维护这些关联数据。
Cloud Bin Bucket
Laf 会为每个应用自动创建一个名为{appid}-cloud-bin的存储桶,用于存储应用的缓存和备份文件。该存储桶默认权限为private,建议不要修改该存储桶的权限。
其实现位于 server/src/storage/cloud-bin-bucket.service.ts:ensureCloudBinBucket(appid)会先查询{appid}-cloud-bin是否已存在,不存在时以BucketPolicy.private策略自动创建:
async ensureCloudBinBucket(appid: string) { const shortName = `cloud-bin` const bucketName = `${appid}-${shortName}` const bucket = await this.bucketService.findOne(appid, bucketName) if (bucket) { return bucket } const dto = new CreateBucketDto() dto.shortName = shortName dto.policy = BucketPolicy.private const created = await this.bucketService.create(appid, dto) return created }此外,该服务还提供createPullUrl/createPushUrl两个方法,基于getSignedUrl为指定文件生成有效期 7 天(expiresIn: 3600 * 24 * 7)的拉取/推送签名 URL,从源码结构看这正是“应用缓存和备份文件”在私有桶中安全读写的通道。
小结
Laf 云存储围绕“S3 兼容 + 应用隔离”两个核心设计展开:
- 应用隔离:桶名强制
{appid}-前缀,SDK 自动拼接、全局唯一且限 63 字符内的小写字母/数字/-; - 权限模型:
public/readonly/private三档,权限决定了使用公共地址还是签名地址访问文件; - 网站托管:
readonly桶可一键托管为静态网站,支持自动域名与自定义域名 + 自动 HTTPS 证书; - Cloud Bin:每应用私有系统桶
{appid}-cloud-bin,承载缓存与备份,不应变更其权限。
相关文档可进一步参考 上传文件、读取文件、生成下载地址、生成上传地址、文件列表、删除文件 与 网站托管;实现细节可深入 packages/cloud-sdk/src/storage.ts、server/src/storage/bucket.service.ts 与 server/src/storage/cloud-bin-bucket.service.ts。
【免费下载链接】lafLaf is a vibrant cloud development platform that provides essential tools like cloud functions, databases, and storage solutions. It enables developers to quickly unleash their creativity and bring innovative ideas to life with ease.项目地址: https://gitcode.com/GitHub_Trending/la/laf
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考