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 客户端从初始化到执行三个核心操作的完整流程,以及可能出现的错误类型。

S3 客户端初始化与核心操作流程 S3Client 初始化 GetObjectCommand PutObjectCommand DeleteObjectCommand s3Client.send(command) 成功:返回数据 / ETag 失败:抛出 S3ServiceException 常见错误:NoSuchKey(404)| NoSuchBucket(404)| AccessDenied(403)| InternalError(500)

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 三个核心命令。这些操作是后续中间件拦截与日志链路追踪的直接作用对象,请务必亲手编写并测试每个命令的调用。