NestJS: API загрузки и обслуживания изображений

Я попытался создать API для загрузки и получения изображений с помощью NestJS. Изображения должны храниться на S3.

Что у меня сейчас есть:

Контроллер

@Post()
@UseInterceptors(FileFieldsInterceptor([
    {name: 'photos', maxCount: 10},
]))
async uploadPhoto(@UploadedFiles() files): Promise<void> {
    await this.s3Service.savePhotos(files.photos)
}


@Get('/:id')
@Header('content-type', 'image/jpeg')
async getPhoto(@Param() params,
               @Res() res) {
    const photoId = PhotoId.of(params.id)
    const photoObject = await this.s3Service.getPhoto(photoId)
    res.send(photoObject)
}

S3Service

async savePhotos(photos: FileUploadEntity[]): Promise<any> {
    return Promise.all(photos.map(photo => {
        const filePath = `${moment().format('YYYYMMDD-hhmmss')}${Math.floor(Math.random() * (1000))}.jpg`
        const params = {
            Body: photo.buffer,
            Bucket: Constants.BUCKET_NAME,
            Key: filePath,
        }
        return new Promise((resolve) => {
            this.client.putObject(params, (err: any, data: any) => {
                if (err) {
                    logger.error(`Photo upload failed [err=${err}]`)
                    ExceptionHelper.throw(ErrorCodes.SERVER_ERROR_UNCAUGHT_EXCEPTION)
                }
                logger.info(`Photo upload succeeded [filePath=${filePath}]`)
                return resolve()
            })
        })
    }))
}

async getPhoto(photoId: PhotoId): Promise<AWS.S3.Body> {
    const object: S3.GetObjectOutput = await this.getObject(S3FileKey.of(`${Constants.S3_PHOTO_PATH}/${photoId.value}`))
        .catch(() => ExceptionHelper.throw(ErrorCodes.RESOURCE_NOT_FOUND_PHOTO)) as S3.GetObjectOutput
    logger.info(JSON.stringify(object.Body))
    return object.Body
}

async getObject(s3FilePath: S3FileKey): Promise<S3.GetObjectOutput> {
    logger.info(`Retrieving object from S3 s3FilePath=${s3FilePath.value}]`)
    return this.client.getObject({
        Bucket: Constants.BUCKET_NAME,
        Key: s3FilePath.value
    }).promise()
        .catch(err => {
            logger.error(`Could not retrieve object from S3 [err=${err}]`)
            ExceptionHelper.throw(ErrorCodes.SERVER_ERROR_UNCAUGHT_EXCEPTION)
        }) as S3.GetObjectOutput
}

Фотообъект фактически попадает в S3, но когда я его загружаю, я не могу его открыть. То же самое для GET => не может быть отображено.

Какую общую ошибку (-ы) я делаю здесь?

См. также:  Redshift Spectrum намного медленнее Афины?
Понравилась статья? Поделиться с друзьями:
IT Шеф
Комментарии: 2
  1. tpschmidt

    Не уверен, какие значения вы возвращаете своему потребителю и какие значения они используют, чтобы снова получить изображение; Не могли бы вы опубликовать, как выглядит фактический ответ, каков запрос и проверьте, совпадают ли полное доменное имя и путь? Похоже, вы тоже забыли про ACL, т.е. ресурсы, которые вы загружаете таким образом, по умолчанию не public-read.

    Кстати, вы можете использовать aws SDK там:

    import { Injectable } from '@nestjs/common'
    import * as AWS from 'aws-sdk'
    import { InjectConfig } from 'nestjs-config'
    import { AwsConfig } from '../../config/aws.config'
    import UploadedFile from '../interfaces/uploaded-file'
    
    export const UPLOAD_WITH_ACL = 'public-read'
    
    @Injectable()
    export class ImageUploadService {
      s3: AWS.S3
      bucketName
      cdnUrl
    
      constructor(@InjectConfig() private readonly config) {
        const awsConfig = (this.config.get('aws') || { bucket: '', secretKey: '', accessKey: '', cdnUrl: '' }) as AwsConfig // read from envs
        this.bucketName = awsConfig.bucket
        this.cdnUrl = awsConfig.cdnUrl
        AWS.config.update({
          accessKeyId: awsConfig.accessKey,
          secretAccessKey: awsConfig.secretKey,
        })
        this.s3 = new AWS.S3()
      }
    
      upload(file: UploadedFile): Promise<string> {
        return new Promise((resolve, reject) => {
          const params: AWS.S3.Types.PutObjectRequest = {
            Bucket: this.bucketName,
            Key: `${Date.now().toString()}_${file.originalname}`,
            Body: file.buffer,
            ACL: UPLOAD_WITH_ACL,
          }
          this.s3.upload(params, (err, data: AWS.S3.ManagedUpload.SendData) => {
            if (err) {
              return reject(err)
            }
            resolve(`${this.cdnUrl}/${data.Key}`)
          })
        })
      }
    
    }
    

    спасибо за Ваш ответ. Мои проблемы связаны с тем, что я не включил поддержку двоичного кода для своего шлюза API. Загрузки теперь работают и правильно сохраняются в s3. ПОЛУЧИТЬ все еще не работает person tpschmidt; 04.10.2019

    не требуется :) Я хочу получить файлы только с помощью лямбда-функции person tpschmidt; 09.10.2019

  2. tpschmidt

    Для тех, у кого такие же проблемы, я наконец понял это:

    Я включил двоичную поддержку на API Gateway (<your-gateway> Settings -> Binary Media Types -> */*), а затем вернул все ответы из лямбда-кодировки base64. API Gateway выполнит декодирование автоматически перед возвратом ответа клиенту. С помощью serverless express вы можете легко включить автоматическое кодирование base64 при создании сервера:

    const BINARY_MIME_TYPES = [
        'application/javascript',
        'application/json',
        'application/octet-stream',
        'application/xml',
        'font/eot',
        'font/opentype',
        'font/otf',
        'image/jpeg',
        'image/png',
        'image/svg+xml',
        'text/comma-separated-values',
        'text/css',
        'text/html',
        'text/javascript',
        'text/plain',
        'text/text',
        'text/xml',
    ]
    
    async function bootstrap() {
        const expressServer = express()
        const nestApp = await NestFactory.create(AppModule, new ExpressAdapter(expressServer))
        await nestApp.init()
    
        return serverlessExpress.createServer(expressServer, null, BINARY_MIME_TYPES)
    }
    

    Теперь в контроллере вы можете просто вернуть тело ответа S3:

    @Get('/:id')
    async getPhoto(@Param() params,
                   @Res() res) {
        const photoId = PhotoId.of(params.id)
        const photoObject: S3.GetObjectOutput = await this.s3Service.getPhoto(photoId)
        res
            .set('Content-Type', 'image/jpeg')
            .send(photoObject.Body)
    }
    

    Надеюсь, это кому-то поможет!

Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: