🌁 Presigned URL 이미지 μ—…λ‘œλ“œ κ΅¬ν˜„ν•˜κΈ°

YeonnΒ·2025λ…„ 3μ›” 15일
0

κΈ°λŠ₯ κ΅¬ν˜„ν•˜κΈ°

λͺ©λ‘ 보기
10/10
post-thumbnail

πŸ’‘ Presigned URL ?

AWS S3μ—μ„œ νŠΉμ • νŒŒμΌμ„ μ—…λ‘œλ“œν•  수 μžˆλ„λ‘ μ œν•œλœ μ‹œκ°„ λ™μ•ˆ μœ νš¨ν•œ μ„œλͺ…λœ URL을 λ°œκΈ‰ν•˜λŠ” κΈ°λŠ₯
이λ₯Ό ν™œμš©ν•˜λ©΄ μ„œλ²„λ₯Ό κ±°μΉ˜μ§€ μ•Šκ³  ν΄λΌμ΄μ–ΈνŠΈμ—μ„œ 직접 S3에 이미지λ₯Ό μ—…λ‘œλ“œ ν•  수 μžˆλ‹€.

  • μ„œλ²„λ‘œ νŒŒμΌμ„ μ „μ†‘ν•˜κ³  λ‹€μ‹œ S3둜 μ—…λ‘œλ“œ ν•˜λŠ” 것이 μ•„λ‹ˆλΌ ν΄λΌμ΄μ–ΈνŠΈκ°€ 직접 S3둜 μ—…λ‘œλ“œ ν•  수 μžˆμ–΄ μ„œλ²„μ˜ λΆ€ν•˜λ₯Ό 쀄일 수 있고,
  • 직접 μ—…λ‘œλ“œ ν•  경우 S3 κΆŒν•œμ΄ λ…ΈμΆœλ  μœ„ν—˜μ΄ μžˆμ§€λ§Œ, Presigned URL을 톡해 μ œν•œλœ μ‹œκ°„λ§Œ μœ νš¨ν•œ URL을 λ°œκΈ‰λ°›μ•„ μ‚¬μš©ν•˜λ―€λ‘œ λ³΄μ•ˆμ„ κ°•ν™”ν•  수 μžˆλ‹€.
  • 또 μ„œλ²„λ₯Ό κ±°μΉ˜μ§€ μ•Šκ³  μ—…λ‘œλ“œ λ˜λ―€λ‘œ λ„€νŠΈμ›Œν¬ 지연을 μ΅œμ†Œν™”ν•  수 μžˆλ‹€.

❓ Presigned URL 이미지 μ—…λ‘œλ“œ 방식

  • ν΄λΌμ΄μ–ΈνŠΈμ—μ„œ 파일 정보λ₯Ό ν¬ν•¨ν•œ μš”μ²­μ„ λ°±μ—”λ“œμ— 보냄
  • λ°±μ—”λ“œμ—μ„œ Presigned URL을 μƒμ„±ν•˜μ—¬ 응닡
  • ν΄λΌμ΄μ–ΈνŠΈμ—μ„œ ν•΄λ‹Ή Presigned URL을 μ‚¬μš©ν•˜μ—¬ S3에 직접 μ—…λ‘œλ“œ
  • μ—…λ‘œλ“œκ°€ μ™„λ£Œλ˜λ©΄ 이미지 URL을 μ„œλ²„μ— μ €μž₯

❗️ Presigned URL 이미지 μ—…λ‘œλ“œ κ΅¬ν˜„ν•˜κΈ°

πŸ“ S3에 이미지 μ—…λ‘œλ“œ ν•˜κΈ°

// utils/uploadImageToS3.ts

const getPresignedUrl = async (file: File, type: string) => {
  try {
    const fileName = file.name;
    const fileType = file.type;

    const response = await axios.post(`${BASE_URL}/upload/${type}`, {
      fileName,
      fileType,
    });

    if (response.data) {
      const { presignedUrl, s3ObjectUrl } = response.data;
      return { presignedUrl, s3ObjectUrl };
    }
    throw new Error("Presigned URL not found in response.");
  } catch (error) {
    console.error("😭 Failed to fetch presigned URL: ", error);
    throw error;
  }
};


const uploadImageToS3 = async (file: File, type: string) => {
  try {
    const fileType = file.type;

    // Presigned URL μš”μ²­
    const { presignedUrl, s3ObjectUrl } = await getPresignedUrl(file, type);

    // Presigned URL을 μ΄μš©ν•΄ 파일 μ—…λ‘œλ“œ
    await axios.put(presignedUrl, file, {
      headers: {
        "Content-Type": fileType,
      },
    });

    return s3ObjectUrl;
  } catch (error) {
    console.error("😭 Image upload failed: ", error);
    throw new Error("Image upload failed");
  }
};

export default uploadImageToS3;

πŸ“ 이미지 μ—…λ‘œλ“œ μ»€μŠ€ν…€ ν›…

// μ‚¬μš©μžκ°€ 이미지 μ„ νƒμ‹œ μ‹€ν–‰
// hooks/useHandleImageChange.tsx
import { useState, useRef } from "react";
import { toast } from "react-toastify";

import uploadImageToS3 from "utils/uploadImageToS3";

const useHandleImageChange = (type: string) => {
  const imgInputRef = useRef<HTMLInputElement | null>(null);
  const [preview, setPreview] = useState<string>("");
  const [hasFile, setHasFile] = useState<boolean>(false);

  const handleImageChange = async () => {
    const files = imgInputRef.current?.files;

    if (!files || files.length === 0) {
      toast.error("이미지가 μ„ νƒλ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.");
      return;
    }

    const file = files[0];

    try {
      // ploadImageToS3λ₯Ό ν˜ΈμΆœν•˜μ—¬ 이미지λ₯Ό μ—…λ‘œλ“œ ν›„ 미리보기 URL μ—…λ°μ΄νŠΈ
      const uploadedImageUrl = await uploadImageToS3(file, type);
      setPreview(uploadedImageUrl);
      setHasFile(true);
    } catch (error) {
      toast.error("이미지 μ—…λ‘œλ“œ 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.");
    }
  };

  return {
    imgInputRef,
    preview,
    hasFile,
    handleImageChange,
  };
};

export default useHandleImageChange;

πŸ“ Presigned URL을 ν™œμš©ν•˜μ—¬ 이미지 μ—…λ‘œλ“œ ν•˜κΈ°

const { imgInputRef, preview, hasFile, handleImageChange } =
    useHandleImageChange("product");

 <S.UploadImgBox onClick={handleImgInputClick}>
	{hasFile ? (
		<>
			<S.UploadedImg src={preview} alt="미리보기" />
		</>
	) : (
		<>
            <S.UploadIcon />
            <S.UploadText>이미지 등둝</S.UploadText>
            <S.Essential>ν•„μˆ˜ 등둝</S.Essential>
        </>
    )}
  <S.ImgUpload
    type="file"
    accept="image/jpg, image/jpeg"
    multiple
    ref={imgInputRef}
    onChange={handleImageChange}
    />
</S.UploadImgBox>
{hasFile && <S.EditImgBtn>사진 μˆ˜μ •ν•˜κΈ°</S.EditImgBtn>}

μ‚¬μš©μžκ°€ 이미지λ₯Ό μ„ νƒν•˜λ©΄ handleImageChangeκ°€ μ‹€ν–‰ !
이미지 μ—…λ‘œλ“œκ°€ μ„±κ³΅ν•˜λ©΄ preview μƒνƒœκ°€ μ—…λ°μ΄νŠΈ 되고, 미리보기 이미지가 ν‘œμ‹œλœλ‹€.
이미지가 있으면 '사진 μˆ˜μ •ν•˜κΈ°' λ²„νŠΌμ΄ λ‚˜νƒ€λ‚œλ‹€.

✨ κ΅¬ν˜„ κ²°κ³Ό

0개의 λŒ“κΈ€