AWS/S3

[AWS 3S] React에서 S3에 이미지 업로드하기

찰리-누나 2023. 1. 1.

 

 

처음 웹개발을 배우는 사람들은, 파일 저장을 배울 때 이렇게 배웠을 것이다. '백엔드에 formData를 전달해, 로컬 서버(내 컴퓨터)에 파일을 저장한다' 고 ...(나도 그랬음)

 

하지만 S3는 내 컴퓨터가 아닌 외부 서버이다. 즉, 남의 컴퓨터에 파일을 저장한다는 뜻이다. 따라서 굳이 백엔드로 전달해 어쩌구 라이브러리를 써서 설정한 다음 내 컴퓨터(로컬)에 저장하고 뭐시기.... 라는 긴 과정을 거칠 필요가 없다. 

 

aws의 aws-sdk 라는 라이브러리를 설치해 사용 방법 대로만 해주면, 프론트엔드에서 바로 S3로 파일을 전송해 저장할 수 있다. S3의 버킷은 우리에게 URL 형태의 파일을 제공해주므로 그를 돌려받아 페이지에 바로 뿌릴 수도 있다. 물론 multeraws-sdk, multer-3s를 이용하면 Node.js 환경의 백엔드에서 동작하게 하는 것 또한 당연히 가능한다. 두가지 모두 실습해 볼 것인데, 먼저 프론트에서 처리하는 법을 실습해 볼 것이다.

 

 

 

 

React에서 aws-sdk 를 이용하여 내 파일 바구니에 이미지를 저장해보자.

벌써 Node.js보다 간단한 것 같다. 실제로 얼마 안 걸린다.

 


 

 

 



React에서 aws-sdk로 S3 버킷에 이미지 저장하기

 

 

 

 

사전 준비로는 IdentityPoolId 이 필요하다. 지난 시간에 진행한 것(LINK)을 통하여 IdentityPoolId을 취득하였다. 복사해서 살포시 텍스트 파일에 저장해두자.

 

 

프론트엔드를 만들어주었다. (css파일을 따로 만들 필요 없도록 그냥 style 태그를 이용하였다.

function Previews() { 이부분만  }  을 App.js에 복사해 붙여넣기 해서 사용해도 되고, Previews라는 컴포넌트를 만들어 이용해도 된다. 아래와 같이 생긴 간단한 앱이다. 이미지를 업로드하면 미리보기를 제공한다. 제목을 입력하면 Axios를 통해 mongodb에 저장할 것이지만, 이 글에서 해당 부분은 구현하지 않는다.

 

 

 

이 글을 통해 구현할 부분은 [업로드]를 눌렀을 때 S3에 저장하게 하는 코드이다. 따라서 아래 코드에는 해당 부분이 아직 구현되어 있지 않다. S3 저장이 모두 구현된 풀버전 코드는 글 맨 밑에 작성하였다.

 

더보기

 

import React, { useRef, useState } from "react";

function Previews() {

  const uploadIcon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA0klEQVR4nO2UWQ6CQBAF+8cbavjQ4+BJ1VNAUgaXxI1lIJPuwVcJP8yQVE1nMBMiLkDVPVYiwA5ogBY4WKHyT8qJ4Fu+nAj65eNHcL+wn/JNz7uqhJNvgT2w7VmLMYkh+Zc9MSOYIB82ggT5cBHMkA8Twfiv8saP78Zos0dMlZ8ZkDciRX5BQJ6IVPmFAW4X+43UdXcU4I0m4I0m4I0m4I0m4M3qJjCGRQO4MJ2TRQM4JgTUFg1g84g4D4h3a3W319tXCCEyQXBMAc7Y6icgxJ9xBUOuyY7C/8ZJAAAAAElFTkSuQmCC'
  const imageInput = useRef();  


  const[files,setFiles] = useState('')
  const [imageSrc, setImageSrc] = useState('');

  const [imgname,setImgname] = useState('')


  // <=========== Savebutton Click event ===========>

  

  function saveEventhandler() {  
  
  // [업로드] 버튼 클릭이벤트
          

  
  } 
  
const onClickInput = () => {
  imageInput.current.click();
  }   


  const onLoadFile = (e) => {

    const file = e.target.files;
    console.log('onLoadFile',file)
    setFiles(file);
    console.log('state에 저장 완료 files',files)

    let fileBlob = e.target.files[0]
    
      const reader = new FileReader();
      reader.readAsDataURL(fileBlob);
      return new Promise((resolve) => {
        reader.onload = () => {
          setImageSrc(reader.result);
          resolve();
        };
      });

  }
  

  return(
    <div style={{position:'relative'}}>
      <div>
        <input type="file" 
        style={{display:'none'}} 
        
        onChange={onLoadFile}
        ref={imageInput} 
        />
        
        <img 
        style={{
          position:'absolute',
          top:'20%',
          left:'44%',
          cursor:'pointer'
        }}
        src={ imageSrc ? '' : uploadIcon}
        onClick={onClickInput}/>
        
        </div>
        
        <img

        className="uploadImage"
        style={{
         minWidth:'150px', minHeight:'150px',maxWidth:'500px', border:'1px solid lightgray',
        alignItems:'center', justifyContent:'center', cursor:'pointer'
       
        }}
        onClick={onClickInput}
        
        src={imageSrc? imageSrc : ''}/>

      <div style={{marginTop:'50px'}} >
        <input type="text" placeholder="제목을 입력해주세요."
      style={{width:'300px',height:'30px',outline:'none',
      fontSize:'25px', color:'white',
      borderLeftWidth:0,borderRightWidth:0,borderTopWidth:0,borderBottomWidth:1,
      backgroundColor:"transparent"
      }}
      onChange={(e)=>{setImgname(e.target.value)}}
      /><span 
      style={{marginLeft:'20px', fontSize:'20px', cursor:'pointer' }}
      onClick={saveEventhandler}
      className="saveButton"
      >업로드</span>
      </div>
      

    </div>
  )
}

  export default Previews;

 

 

 

 

 

React 프로젝트에서 aws-sdk를 다운로드 해 준다.

npm install aws-sdk

 

src 폴더에 config 폴더를 만들고, config.js 파일을 만들어 복사해둔 region과 IdentityPoolld를 등록해준다.

export const awsRegion = '나의region을 이곳에 입력하세요 서울은 ap-northeast-2입니다';
export const awsIdentityPoolId = '나의id를 이곳에 입력하세요'

 

Previews.js 파일로 돌아와 config를 import 해 주고, 아래 코드를 추가한다. AWS.config.update는 AWS를 사용하기 위해 설정해 주어야 하는 기본값이다. 여기에 config에 저장한 지역명과, id를 연결해 주었다.

import * as config from 'config파일 위치'
import AWS from "aws-sdk"


function Previews() {

   AWS.config.update({
    region: config.awsRegion, // 버킷이 존재하는 리전을 문자열로 입력합니다. (Ex. "ap-northeast-2")
    credentials: new AWS.CognitoIdentityCredentials({
      IdentityPoolId: config.awsIdentityPoolId, // cognito 인증 풀에서 받아온 키를 문자열로 입력합니다. (Ex. "ap-northeast-2...")
    }),
  })
  
  ...

 

 

본격적으로 파일을 저장하기 위한 코드를 짜보자. [업로드] 버튼을 누르면 실행할 코드를 작성한다.

먼저, 사용자가 업로드할 파일의 크기를 제한해준다. 10mb 를 초과하는 파일은 업로드하지 못하게 할 것이다.

    function saveEventhandler() {  
      
      if (files && files[0].size > (10 * 1024 * 1024)) {
          alert("10mb 이하의 파일만 업로드할 수 있습니다.");
       } else {

 

 

 

 

const files에 onLoad를 통해, 업로드된 파일이 임시 저장되어있다. 이 files의 [0]번째 배열에는 파일의 정보가 들어있다. 이를 변수를 선언해 저장해준다.

 const uploadFiles = files[0]

 

 

 

이제 S3에 파일을 저장하기 위한 준비를 해보자. ManagedUpload를 통해, 파일을 업로드 할 수 있다. params에는 버킷 이름확장자를 포함한 업로드할 파일 이름, 업로드할 파일 그 자체를 넣어주어야 한다. 

참고로, files[0].name에는 확장자를 포함한 파일의 풀네임이 들어있다. Key에 uploadFiles.name을 해 주면, 따로 파일 확장자를 추출해 설정하지 않아도 된다.

   // Todo S3에 파일을 저장한다.
          const upload = new AWS.S3.ManagedUpload({
            params: {
              Bucket: '버킷이름', // 업로드할 대상 버킷명
              Key: '업로드할 파일이름' + '.확장자' , //파일 이름과 확장자
              Body: uploadFiles, // 업로드할 파일 객체
            },
          })

 

 

 

만일 파일 이름과 확장자를 추출하고 싶다면 아래 코드를 사용하면 된다.(나는 사용하지 않았음)

// 파일 이름 추출
const fileName = files[0].name.substring(0,files[0].name.lastIndexOf('.'))
// 파일 확장자 추출
const fileExtension = files[0].name.substring(files[0].name.lastIndexOf('.'),files[0].name.length).toLowerCase()

 

 

 

promise 문법을 사용해, '업로드가 성공했을 경우' 와 '실패했을 경우'를 나누어 처리해주자. async-await을 사용해도 된다. promise가 성공하면, data에 성공한 데이터가 넘겨져온다.

 const promise = upload.promise()

          promise.then(
            function (data) {
              alert("이미지 업로드에 성공했습니다.")
              console.log('업로드한 데이터',data)
            },
            function (err) {
              return alert("오류가 발생했습니다: ", err.message)
            }
          )

 

 

이를 합치면 아래 코드가 된다.

 

function saveEventhandler() {  
      
        if (files && files[0].size > (10 * 1024 * 1024)) {
          alert("10mb 이하의 파일만 업로드할 수 있습니다.");
       } else {

        const uploadFiles = files[0]          
        
          // Todo S3에 파일 저장 후 response로 파일 링크 받아오기
          const upload = new AWS.S3.ManagedUpload({
            params: {
              Bucket: '버킷이름', // 업로드할 대상 버킷명
              Key: '확장자를 포함한 파일명', //파일명+확장자
              Body: uploadFiles, // 업로드할 파일 객체
            },
          })

          const promise = upload.promise()

          promise.then(
            function (data) {
              alert("이미지 업로드에 성공했습니다.")
              },
            function (err) {
              return alert("오류가 발생했습니다: ", err.message)
            }
          )
       }     

  
  }

 

 

 

s3 버킷에 이미지가 저장되는지 테스트 해 보자. 고양이 사진을 넣고 [업로드] 버튼을 클릭한 후에, 버킷을 새로고침 해 주었다.

 

 

성공적으로 저장되었다. [키]는 파일 이름, [객체 url] 은 파일을 다운할 수 있게 해주는 url이다.

 

다른 파일을 다시 올리고, promise가 성공했을 경우 전달받은 data를 콘솔로 출력해 본다. 이를 활용하면 버킷에 있는 데이터를 불러오고, 화면에 로드할 수 있을 것이다.

콘솔 출력 내용

 

 

버킷에 있는 정보를 불러와 조회하는 것은 다음 글에서 해본다.

 

 

 

 

 


 

 

 

전체 코드

 

더보기

 

import React, { useRef, useState } from "react";
import axios from 'axios';
import AWS from "aws-sdk"
import * as config from '../config/config'

function Previews() {

  //  =========================== set AWS ===========================
  
  AWS.config.update({
    region: config.awsRegion, // 버킷이 존재하는 리전 (Ex. "ap-northeast-2")
    credentials: new AWS.CognitoIdentityCredentials({
      IdentityPoolId: config.awsIdentityPoolId, // cognito 인증 풀에서 받아온 키 (Ex. "ap-northeast-2...")
    }),
  })
  

  const uploadIcon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA0klEQVR4nO2UWQ6CQBAF+8cbavjQ4+BJ1VNAUgaXxI1lIJPuwVcJP8yQVE1nMBMiLkDVPVYiwA5ogBY4WKHyT8qJ4Fu+nAj65eNHcL+wn/JNz7uqhJNvgT2w7VmLMYkh+Zc9MSOYIB82ggT5cBHMkA8Twfiv8saP78Zos0dMlZ8ZkDciRX5BQJ6IVPmFAW4X+43UdXcU4I0m4I0m4I0m4I0m4M3qJjCGRQO4MJ2TRQM4JgTUFg1g84g4D4h3a3W319tXCCEyQXBMAc7Y6icgxJ9xBUOuyY7C/8ZJAAAAAElFTkSuQmCC'
  const imageInput = useRef();  


  const[files,setFiles] = useState('')
  const [imageSrc, setImageSrc] = useState('');

  const [imgname,setImgname] = useState('')


  // <=========== Savebutton Click event ===========>
  

  function saveEventhandler() {  
      
        if (files && files[0].size > (10 * 1024 * 1024)) {
          alert("10mb 이하의 파일만 업로드할 수 있습니다.");
       } else {

        const uploadFiles = files[0]          
        
          // S3에 파일 저장
          
          const upload = new AWS.S3.ManagedUpload({
            params: {
              Bucket: '버킷이름', // 업로드할 대상 버킷명
              Key: `${Date.now()}_${uploadFiles.name}`, //업로드할 파일 이름(확장자 포함)
              Body: uploadFiles, // 업로드할 파일 객체
            },
          })

          const promise = upload.promise()

          promise.then(
            function (data) {
              alert("이미지 업로드에 성공했습니다.")
             },
            function (err) {
              return alert("오류가 발생했습니다: ", err.message)
            }
          )

        }

      

  
  } 
  
const onClickInput = () => {
  imageInput.current.click();
  }   


  const onLoadFile = (e) => {

    const file = e.target.files;
    console.log('onLoadFile',file)
    setFiles(file);

    let fileBlob = e.target.files[0]
    
      const reader = new FileReader();
      reader.readAsDataURL(fileBlob);
      return new Promise((resolve) => {
        reader.onload = () => {
          setImageSrc(reader.result);
          resolve();
        };
      });

  }
  

  return(
    <div style={{position:'relative'}}>
      <div>
        <input type="file" 
        style={{display:'none'}} 
        
        onChange={onLoadFile}
        ref={imageInput} 
        />
        
        <img 
        style={{
          position:'absolute',
          top:'20%',
          left:'44%',
          cursor:'pointer'
        }}
        src={ imageSrc ? '' : uploadIcon}
        onClick={onClickInput}/>
        
        </div>
        
        <img

        className="uploadImage"
        style={{
         minWidth:'150px', minHeight:'150px',maxWidth:'500px', border:'1px solid lightgray',
        alignItems:'center', justifyContent:'center', cursor:'pointer'
       
        }}
        onClick={onClickInput}
        
        src={imageSrc? imageSrc : ''}/>

      <div style={{marginTop:'50px'}} >
        <input type="text" placeholder="제목을 입력해주세요."
      style={{width:'300px',height:'30px',outline:'none',
      fontSize:'25px', color:'white',
      borderLeftWidth:0,borderRightWidth:0,borderTopWidth:0,borderBottomWidth:1,
      backgroundColor:"transparent"
      }}
      onChange={(e)=>{setImgname(e.target.value)}}
      /><span style={{marginLeft:'20px', fontSize:'20px', cursor:'pointer' }}
      onClick={saveEventhandler}
      >업로드</span>
      </div>
      

    </div>
  )
}

  export default Previews;

 

 

댓글