Nextjs 에서 MULTER로 이미지 업로드 - 3- (lamda로 이미지 리사이징)

column clash·2021년 10월 2일
1
post-custom-banner

지난 1편에선 로컬에서 NextJs 의 API 서버에서 Multer 사용법을
2편에선 s3 에 업로드 하는 것을 기록해두었다.

이제 3편에서는 lamda 로 이미지 리사이징 하는 것을 기록해둔다.

  1. 루트폴더내에 lamda 폴더 생성
  2. npm init 엔터엔터 엔터~ 마지막에 yes
  3. yarn add aws-sdk sharp
    (윈도우에서 aws 용 배포를 만드려면, 이렇게 하면 오류가 나고,
    git bash 로 작업을 해야한다. https://dev.uhoon.co.kr/entry/aws-lambda-%EB%B0%B0%ED%8F%AC-%ED%8C%A8%ED%82%A4%EC%A7%80-%EC%83%9D%EC%84%B1)
  4. index.js 생성
  5. 코드작성
import AWS from "aws-sdk"
import sharp from "sharp"

const s3 = new AWS.S3();

exports.handler = async(event, context, callback) => {
    const Bucket = event.Records[0].s3.Bucket.name;   //cultureplace
    const Key = decodeURIComponent(event.Records[0].s3.object.key);  // original/123_abc.png
    console.log("Bucket", Bucket, "Key", Key);
    const filename = Key.split("/")[Key.split("/").length - 1];
    const ext = Key.split(".")[Key.split(".").length - 1];
    const requiredFormat = ext === "jpg" ? "jpeg" : ext;
    console.log("filename", filename, "ext", ext);

    try {
        const s3Object = await s3.getObject({ Bucket, Key }).promise();
        console.log("original", s3Object.Body.length);
        const resizedImage = await sharp(s3Object.Body).resize(400, 400, { fit: "inside" }).toFormat(requiredFormat).toBuffer();
        await s3
          .putObject({
            Bucket,
            Key: `thumb/${filename}`,
            Body: resizedImage,
          })
            .promise();
        console.log("put", resizedImage.length);
        return callback(null, `thumb/${filename}`)
    } catch (error) {
        console.error(error)
        return callback(error)
    }
        

}

aws-upload.zip 로 lamda 폴더내에서 압축.

  1. aws cli 설치

최신 버전의 AWS CLI의 경우:
https://awscli.amazonaws.com/AWSCLIV2.msi

제대로 동작하는지 보려면, cmd 에서 aws --version 체크 (만약 vc 터미널에서 반영안됐다면
vc 프로그램 종료 후 다시 시작)

  1. 1번에서 생성했던 lamda 로 이동 후, aws configure
  2. AWS Access Key ID, AWS Secret Access Key (지난 2편에서 받은 키를 입력)
  3. aws s3 cp "aws-upload.zip" s3://cultureplace (s3로 파일 업로드 명령어)
  4. s3폴더에 잘들어왔는지 확인
  5. lamda 로 이동
    (https://ap-northeast-2.console.aws.amazon.com/lambda)
  6. 함수 생성 클릭
  7. 함수 이름 : image-resize
    13_2. 권한 > 기본 실행 역할 변경 > aws 정책 템플릿에서 새역할 생성 => 역할이름은 image-resize, 정책 템플릿은
    Amazon S3 객체 읽기 전용 권한
  8. 코드 소스 에서 에서 업로드 셀렉트 박스 클릭 : Amazon S3 위치
  9. https://버킷주소.s3.ap-northeast-2.amazonaws.com/aws-upload.zip
  10. 트리거 추가
  11. s3 선택 => 버킷: cultureplace / 이벤트 : 모든 객체 생성 이벤트 / 접두사 original/ 트리거 활성화 (옵션이 있다면) / 재귀호출 체크박스 체크/
  12. 추가 클릭
  13. 구성 => 일반 구성 => 편집
    메모리 256mb
    제한시간 30초

소스코드 변경

api/imgupload/index.js

import nextConnect from "next-connect";
import multer from "multer";
import path from "path";
import dayjs from "dayjs";
import multerS3 from "multer-s3";
import AWS from "aws-sdk";



const app = nextConnect({
  onError(error, req, res) {
    res
      .status(501)
      .json({ error: `Sorry something Happened! ${error.message}` });
  },
  onNoMatch(req, res) {
    res.status(405).json({ error: `Method '${req.method}' Not Allowed` });
  },
});


AWS.config.update({
  accessKeyId: process.env.S3_ACCESS_KEY_ID,
  secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
  region: "ap-northeast-2",
});

var upload = multer({
  storage: multerS3({
    s3: new AWS.S3(),
    bucket: "cultureplace",
    key(req, file, cb) {
      const nowDate = dayjs(Date.now()).format("YYMMDDHHMM");
      cb(null, `original/${nowDate}_${file.originalname}`);
    },
  }),
});


// var upload = multer({ storage: storage });
app.post(upload.array("file"), function (req, res) {
  res.json(req.files.map((v) => v.location.replace(/\/original\//, "/thumb/")));
});

export default app;

export const config = {
  api: {
    bodyParser: false, // Disallow body parsing, consume as stream
  },
};

pages/imgup.tsx

import React, { useCallback, useState } from "react";
import axios from "axios";
import { UiFileInputButton } from "./components/UiFileInputButton";

const IndexPage = () => {
  const [thumb, setThumb] = useState<string[]>([]);
  const [progress, setProgress] = useState<number>(0);
  const onChange = useCallback(
    async (formData: FormData) => {
      const config = {
        headers: { "content-type": "multipart/form-data" },
        onUploadProgress: (event: { loaded: number; total: number }) => {
          setProgress(Math.round((event.loaded * 100) / event.total));
        },
      };
      axios.post<any>("/api/imgupload", formData, config).then((res) => {
        setThumb([...thumb, ...res.data]);
      });
    },
    [thumb]
  );

  return (
    <>
      <p>
        <span>이미지 업로드</span>
        <span>{progress}</span>
      </p>
      <UiFileInputButton
        label="Upload Single File"
        // allowMultipleFiles 가 false 일경우, 하나씩만 올릴 수 있다.
        allowMultipleFiles={true}
        uploadFileName="file"
        onChange={onChange}
      />
      <ul>
        {thumb &&
          thumb.map((item: string, i: number) => {
            console.log("item", item);
            return (
              <li key={i}>
                썸네일
                <p>
                  <img src={item} width="300" alt="업로드이미지" />
                </p>
                <p>
                  원본
                  <img
                    src={item.replace(/\/thumb\//, "/original/")}
                    width="300"
                    alt="업로드이미지"
                  />
                </p>
              </li>
            );
          })}
      </ul>
    </>
  );
};

export default IndexPage;
  1. 모니터링에서 람다가 제대로 작동하는지 볼 수 있음.
    (모니터링 탭의 cloudWatch 로그 확인하면 에러 파악가능)

이렇게 하면, 람다가 이미지 리사이징을 잘 해주는 것을 확인할 수 있다.

react-query 로 Infinity Scroll & Scroll restoration 을 만들어두었는데
오늘 만들어둔 이미지 관리 소스 + wywig 게시판 기능을 활용해서
상품을 등록하고 (상세페이지등록)
무한스크롤을 구현한 예제를 올려보겠다.

profile
풀스택 개발 중...
post-custom-banner

0개의 댓글