Backend Typescript 1.0.0 Help

S3

Введение

Хранилища, совместимые с S3 API, — это объектные системы хранения данных, ориентированные на сохранение файлов (объектов) в «бакетах» (bucket) с простым HTTP-интерфейсом. Самый известный провайдер — Amazon S3, но есть и совместимые решения для собственных серверов и частных облаков: MinIO, Ceph RGW и др. S3-подобные системы масштабируются горизонтально, поддерживают версионирование, шифрование, политики доступа и правила жизненного цикла.

  • Объект — блоб данных + метаданные (Content-Type, пользовательские метки) и ключ (имя).

  • Bucket — логический контейнер для объектов (на уровне API напоминает «папку», но внутри нет настоящей файловой иерархии).

  • Доступ — управляется ключами (Access Key / Secret Key), IAM-политиками, ACL и bucket policy.

  • Совместимость — клиенты обращаются через HTTP(S) и стандартные REST-методы; MinIO реализует тот же S3 API, поэтому SDK для S3 обычно подходят «как есть» при указании кастомного endpoint.

Зачем и когда использовать S3/MinIO

  • Хранение статического контента: изображения, видео, документы, логи, бэкапы.

  • Дешёвое и масштабируемое хранилище для больших данных с высокой надёжностью.

  • Интеграция по HTTP: простая загрузка/выгрузка из приложений, CI/CD и из браузера через предподписанные URL.

  • Локальные и частные инсталляции: MinIO даёт S3-совместимое хранилище on-prem с поддержкой отказоустойчивости и кодирования стираний.

Ключевые понятия S3/MinIO

  • Имена бакетов: глобально уникальны в AWS; в MinIO — в пределах кластера. Обычно строчные, без подчёркиваний.

  • Ключ объекта: строка-идентификатор. Слэши в ключе — просто часть имени (псевдокаталоги).

  • Регион: в AWS обязателен; в MinIO — формальность для совместимости.

  • Path-style vs Virtual-hosted-style: в локальных/MinIO окружениях часто нужен path-style (http://host:9000/bucket/object), а не поддомен.

  • Версионирование: хранит несколько версий одного объекта для откатов и защиты от случайного удаления.

  • Политики: гибко задают права (чтение/запись) на уровне бакета и префиксов.

  • Шифрование: на стороне сервера (SSE) или клиента (CSE); MinIO поддерживает SSE-S3/SSE-KMS и SSE-C.

Минимальный набор операций

  • Создать бакет

  • Загрузить объект (put)

  • Скачать объект (get)

  • Список объектов (list)

  • Удалить объект (delete)

  • Сгенерировать предподписанную ссылку (presign) для безопасной загрузки/скачивания без прямой выдачи ключей

Настройка MinIO (локально)

  • Скачайте MinIO сервер и клиент mc, запустите контейнер или бинарник: minio server /data.

  • Откройте консолиданный веб-интерфейс (по умолчанию http://localhost:9001) или используйте mc для управления.

  • Создайте пользователя/ключи, бакеты, политики.

# Пример запуска Docker docker run -p 9000:9000 -p 9001:9001 \ -e MINIO_ROOT_USER=minioadmin \ -e MINIO_ROOT_PASSWORD=minioadmin \ -v $(pwd)/data:/data \ minio/minio server /data --console-address ":9001" # Настройка алиаса и создание бакета с mc mc alias set local [http://localhost:9000](http://localhost:9000) minioadmin minioadmin mc mb local/my-bucket mc ls local

Используем официальный AWS SDK v2. Для MinIO укажем кастомный endpoint и включим path-style адресацию.

<dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>s3</artifactId> <version>2.25.40</version> </dependency>

---


import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; import software.amazon.awssdk.core.sync.RequestBody; import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.S3Configuration; import software.amazon.awssdk.services.s3.model.*; import java.net.URI; import java.nio.file.Paths; public class MinioS3JavaExample { public static void main(String[] args) { String endpoint = "[http://localhost:9000](http://localhost:9000)"; // MinIO String accessKey = "minioadmin"; String secretKey = "minioadmin"; String bucket = "my-bucket"; String key = "docs/hello.txt"; S3Configuration s3conf = S3Configuration.builder() .pathStyleAccessEnabled(true) // важно для MinIO локально .build(); S3Client s3 = S3Client.builder() .httpClient(UrlConnectionHttpClient.create()) .endpointOverride(URI.create(endpoint)) // убрать для AWS .region(Region.US_EAST_1) // для MinIO любая .credentialsProvider(StaticCredentialsProvider.create( AwsBasicCredentials.create(accessKey, secretKey))) .serviceConfiguration(s3conf) .build(); // Создать бакет (idempotent) try { s3.createBucket(CreateBucketRequest.builder().bucket(bucket).build()); } catch (BucketAlreadyOwnedByYouException | BucketAlreadyExistsException ignored) {} // Загрузка строки s3.putObject(PutObjectRequest.builder() .bucket(bucket) .key(key) .contentType("text/plain") .build(), RequestBody.fromString("Hello S3-compatible world!")); // Загрузка файла s3.putObject(PutObjectRequest.builder() .bucket(bucket) .key("files/report.pdf") .contentType("application/pdf") .build(), Paths.get("./report.pdf")); // Чтение объекта GetObjectResponse meta = s3.getObject(GetObjectRequest.builder() .bucket(bucket) .key(key) .build(), Paths.get("./downloaded-hello.txt")); System.out.println("ETag: " + meta.eTag()); // демонстрация // Список объектов по префиксу ListObjectsV2Response list = s3.listObjectsV2(ListObjectsV2Request.builder() .bucket(bucket) .prefix("docs/") .build()); list.contents().forEach(o -> System.out.println(o.key())); // демонстрация // Удаление s3.deleteObject(DeleteObjectRequest.builder().bucket(bucket).key(key).build()); } }

Библиотека github.com/minio/minio-go/v7 предоставляет удобные методы для всех типичных операций и корректно работает как с MinIO, так и с AWS S3 (при указании соответствующего endpoint).

go get github.com/minio/minio-go/v7

---


package main import ( "context" "fmt" "log" "strings" "time" "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" ) func main() { endpoint := "localhost:9000" accessKeyID := "minioadmin" secretAccessKey := "minioadmin" useSSL := false ctx := context.Background() client, err := minio.New(endpoint, &minio.Options{ Creds: credentials.NewStaticV4(accessKeyID, secretAccessKey, ""), Secure: useSSL, }) if err != nil { log.Fatalln(err) } bucket := "my-bucket" location := "us-east-1" // Создать бакет, если нет exists, err := client.BucketExists(ctx, bucket) if err != nil { log.Fatalln(err) } if !exists { if err := client.MakeBucket(ctx, bucket, minio.MakeBucketOptions{Region: location}); err != nil { log.Fatalln(err) } } // Загрузить объект из строки objName := "docs/hello.txt" content := "Hello from Go + MinIO!" _, err = client.PutObject(ctx, bucket, objName, strings.NewReader(content), int64(len(content)), minio.PutObjectOptions{ContentType: "text/plain"}) if err != nil { log.Fatalln(err) } // Скачать объект в память obj, err := client.GetObject(ctx, bucket, objName, minio.GetObjectOptions{}) if err != nil { log.Fatalln(err) } stat, _ := obj.Stat() fmt.Println("ETag:", stat.ETag) // демонстрация // Список объектов for objInfo := range client.ListObjects(ctx, bucket, minio.ListObjectsOptions{ Prefix: "docs/", Recursive: true, }) { if objInfo.Err != nil { log.Fatalln(objInfo.Err) } fmt.Println("Key:", objInfo.Key) } // Предподписанная ссылка на скачивание (на 15 минут) reqParams := make(urlValues) // пример: reqParams.Set("response-content-disposition", "attachment; filename=hello.txt") presigned, err := client.PresignedGetObject(ctx, bucket, objName, 15*time.Minute, reqParams) if err != nil { log.Fatalln(err) } fmt.Println("Presigned GET:", presigned.String()) // Удаление err = client.RemoveObject(ctx, bucket, objName, minio.RemoveObjectOptions{}) if err != nil { log.Fatalln(err) } } // urlValues — небольшая утилита для наглядности type urlValues map[string][]string func (v urlValues) Set(k, val string) { v[k] = []string{val} }

Ниже — базовые операции и генерация предподписанных URL.

npm i minio

---


import {Module} from '@nestjs/common'; import {ConfigService} from '@nestjs/config'; import {Client as MinioClient, ClientOptions} from 'minio'; import {S3Service} from '~src/data/storages/s3/s3.service'; @Module({ providers: [ { provide: MinioClient, useFactory: (config: ConfigService) => { return new MinioClient(config.getOrThrow<ClientOptions>('s3')); }, inject: [ConfigService] }, S3Service ], exports: [S3Service] }) export class S3Module { }

---


import {Injectable} from '@nestjs/common'; import {ConfigService} from '@nestjs/config'; import {Client as MinioClient} from 'minio'; @Injectable() export class S3Service { constructor( private readonly configService: ConfigService, private readonly client: MinioClient ) { } async uploadFile(key: string, buffer: Buffer, bucket: string): Promise<void> { await this.client.putObject(bucket, key, buffer); } @Trace('S3Service.getDownloadLink') async getDownloadLink( key: string, bucket: string, expiresIn: number = this.configService.getOrThrow<number>('s3.expire-in-links-seconds') ): Promise<string> { return await this.client.presignedGetObject(bucket, key, expiresIn); } async getViewLink( key: string, bucket: string, expiresIn: number = this.configService.getOrThrow<number>('s3.expire-in-links-seconds') ): Promise<string> { const filename = key.split('/').pop(); const params = { 'response-content-disposition': `inline; filename="${filename}.pdf"`, 'response-content-type': 'application/pdf' }; return this.client.presignedGetObject(bucket, key, expiresIn, params); } async getFile(bucket: string, key: string): Promise<Buffer> { try { const stream = await this.client.getObject(bucket, key); const chunks: Buffer[] = []; return await new Promise<Buffer>((resolve, reject) => { stream.on('data', (chunk: Buffer) => chunks.push(chunk)); stream.on('end', () => resolve(Buffer.concat(chunks))); stream.on('error', (err) => reject(err)); }); } catch (err) { throw err; } } async removeFile(key: string, bucket: string) { return await this.client.removeObject(bucket, key); } }

Практические рекомендации и лучшие практики

  • Безопасность ключей: не храните секреты в репозитории. Используйте переменные окружения, менеджеры секретов (AWS Secrets Manager, HashiCorp Vault).

  • Минимальные права: создавайте отдельные пользователей/ролей с доступом только к нужным бакетам/префиксам (принцип наименьших привилегий).

  • Контент-типы и кеширование: задавайте Content-Type, Cache-Control при PutObject — это влияет на отдачу статики.

  • Версионирование и «мягкое» удаление: включите versioning для защиты от случайных удалений/перезаписей.

  • Лайфциклы: переводите старые объекты в «холодные» классы хранения (в AWS) или удаляйте/архивируйте по правилам lifecycle.

  • Большие файлы: используйте мультичастичную загрузку и параллелизм, контролируйте размер частей (например, 8–32 МБ).

  • Надёжность MinIO: в проде используйте распределённый режим с кодированием стираний, как минимум 4 диска/узла для устойчивости к сбоям.

Особенности совместимости и отладки

  • Path-style: для MinIO локально включайте pathStyleAccess/forcePathStyle. Без этого SDK могут пытаться обращаться к поддоменам вида bucket.localhost.

  • HTTPS и сертификаты: в dev можно использовать HTTP, но в проде — только HTTPS. Проверьте доверие к сертификату при соединении.

  • ETag: в S3 часто равен MD5 для одночастных загрузок, но при мультичастичной — это не MD5 содержимого, что важно при проверках целостности.

  • Логи и трассировка: включайте расширенный лог/отладку SDK, а также access logs на стороне MinIO для анализа проблем доступа и CORS.

Last modified: 01 October 2025