3. S3 接口基础:AWS SDK for JavaScript v3 简介、S3 客户端初始化、常用操作
在 NestJS 中间件介入 S3 错误处理与日志链路之前,必须先掌握 AWS SDK for JavaScript v3 的核心用法。本章聚焦 S3 客户端的初始化与三个最常用的数据操作——GetObject、PutObject、DeleteObject,为后续中间件拦截与链路追踪打下基础。
3.1 AWS SDK for JavaScript v3 概述
AWS SDK v3 是 AWS 官方推出的新一代 JavaScript SDK,采用模块化架构,相比 v2 在体积、启动速度和类型支持上均有显著提升。
| 特性 | AWS SDK v2 | AWS SDK v3 |
|---|---|---|
| 包结构 | 单体包(~50MB) | 按服务拆分为独立包(如 @aws-sdk/client-s3) |
| Tree-shaking | 不支持 | 原生支持,减少打包体积 |
| TypeScript 支持 | 类型定义不完整 | 内置完整类型,开箱即用 |
| 中间件栈 | 有限的自定义能力 | 可插拔中间件,便于日志与错误处理 |
| 请求生命周期 | 回调/流式 | 统一 Promise + async/await |
推荐理由:v3 的模块化设计使得在 NestJS 中按需注入 S3 客户端成为可能,且中间件栈与 NestJS 的拦截器/中间件模式天然契合,便于统一管理错误与日志。
3.2 S3 客户端初始化
初始化 S3 客户端是使用 AWS SDK 的第一步。在 NestJS 项目中,通常将客户端封装为可注入的 Provider,以便在服务层统一使用。
3.2.1 基础初始化
import { S3Client } from '@aws-sdk/client-s3';
const s3Client = new S3Client({
region: 'us-east-1',
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
},
});
3.2.2 在 NestJS 中作为 Provider 注册
// s3-client.provider.ts
import { Provider } from '@nestjs/common';
import { S3Client } from '@aws-sdk/client-s3';
export const S3_CLIENT = 'S3_CLIENT';
export const s3ClientProvider: Provider = {
provide: S3_CLIENT,
useFactory: () => {
return new S3Client({
region: process.env.AWS_REGION || 'ap-northeast-1',
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
},
// 可选:设置请求超时与重试策略
requestHandler: {
requestTimeout: 5000,
},
maxAttempts: 3,
});
},
};
最佳实践:将 S3 客户端作为自定义 Provider 注入,便于在测试时替换为 mock 客户端,也方便在中间件中统一注入日志与监控。
3.3 常用操作详解
以下三个操作覆盖了 S3 接口中最核心的数据读写与删除场景。每个操作均展示命令构造、调用方式及返回结构。
3.3.1 GetObject — 下载对象
import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3';
async function getObject(bucket: string, key: string) {
const command = new GetObjectCommand({
Bucket: bucket,
Key: key,
});
const response = await s3Client.send(command);
// response.Body 是一个 ReadableStream,需要转换为字符串或 Buffer
const body = await response.Body.transformToString('utf-8');
return {
data: body,
contentType: response.ContentType,
metadata: response.Metadata,
};
}
3.3.2 PutObject — 上传对象
import { PutObjectCommand } from '@aws-sdk/client-s3';
async function putObject(bucket: string, key: string, body: string | Buffer, contentType?: string) {
const command = new PutObjectCommand({
Bucket: bucket,
Key: key,
Body: body,
ContentType: contentType || 'application/octet-stream',
// 可选:设置服务端加密
ServerSideEncryption: 'AES256',
});
const response = await s3Client.send(command);
return {
etag: response.ETag,
versionId: response.VersionId,
};
}
3.3.3 DeleteObject — 删除对象
import { DeleteObjectCommand } from '@aws-sdk/client-s3';
async function deleteObject(bucket: string, key: string) {
const command = new DeleteObjectCommand({
Bucket: bucket,
Key: key,
});
const response = await s3Client.send(command);
// 删除成功时 response 不包含 Body,仅返回 HTTP 状态码 204
return {
deleteMarker: response.DeleteMarker,
versionId: response.VersionId,
};
}
3.4 操作流程与错误分类
下图展示了 S3 客户端从初始化到执行三个核心操作的完整流程,以及可能出现的错误类型。
3.5 错误类型速查
S3 操作中常见的异常均继承自 S3ServiceException,通过 name 属性可快速区分错误类型。
| 错误名称 | HTTP 状态码 | 触发场景 |
|---|---|---|
| NoSuchKey | 404 | GetObject 时对象不存在 |
| NoSuchBucket | 404 | 指定的存储桶不存在 |
| AccessDenied | 403 | 凭证权限不足 |
| BucketAlreadyExists | 409 | 创建桶时名称已被占用 |
| InternalError | 500 | S3 服务端内部错误 |
注意:在 NestJS 中间件中捕获这些异常时,应优先检查
error.name 而非 error.code,因为 v3 SDK 统一使用 name 属性标识错误类型,而 code 可能在不同版本中不一致。
3.6 小结
本章完成了 S3 接口基础的三步走:理解 v3 模块化优势、掌握客户端初始化与 Provider 注册、熟练使用 GetObject/PutObject/DeleteObject 三个核心命令。这些操作是后续中间件拦截与日志链路追踪的直接作用对象,请务必亲手编写并测试每个命令的调用。