TIL - 이미지 처리과정과 방법

Taewoong Moon·2021년 6월 2일
1

모든 코드에 의미를 담겠습니다.

금일은, 이미지를 어떻게 백엔드에 요청을해서 응답을 받아오는 지 한번 알아보자!!

현재 내가 사용하고 있는 기술 스택은 next.js(react.js)를 통해 Apollo-graphql을 통해서 백엔드 API와 통신을 해서 데이터를 받아오고 있다.

데이터를 Read하는 경우를 제외하고는 Apollo-graphql에서는 mutation을 이용한다. 그래서 이미지를 업로드할때도 그냥 똑같이 Mutation을 하면 되는 줄 알았다.

그러나 이미지 & 동영상 같은 경우는 보통 용량이 큰 경우가 많다. 백엔드 DB에 저장하기에는 이미지 하나당 5MB이렇게 공간을 차지하는 경우가 너무 많은데 텍스트를 받아 저장하는 DB와 묶는건 사실 불가능한 일이다.

그래서!!!

이미지는 스토리지라는 저장공간(쉽게말해, 저장공간이 큰 C드라이브라고 보면 좋을 것 같다.)에 보관을 해둔다. 우리가 아는 유명한 스토리지는 AWS S3 or GCP(Google Cloud Platform) 와 같은것이 있다.

이미지처리 프로세스는 그래서 이와 같다.

클라이언트(Frontend) -> API(백엔드) -> 스토리지

스토리지 -> API(백엔드) -> 클라이언트(프론트엔드)

이런구조인데

apollo-graphql을 통해서 스토리지에도 저장을 하고 DB에도 저장을 해야한다. DB에 저장하는 url은 그냥 텍스트일 뿐이고 클라이언트에서 요청을 할 때 DB에서 보유하고 있던 url을 스토리지에서 식별을해서 스토리지에서 이미지를 클라이언트에 뿌리는 역할을 한다.

자 이론적인 개념은 여기까지고 실제로 어떻게 연결을 하여 데이터를 추가하고 불러오는 지 한번 확인해보자.

내가 백엔드와 통신하는 API는 Apollo-graphql이기 때문에 REST-API로 하시는 분들은 참고만 하시되 REST-API 통신형태로 하는 게 좋을 것 같다.

이미지 처리 프로세스

  1. 아폴로 업로드 클라이언트를 설치한다 (yarn install apollo-upload-client). 아폴로 업로드 클라이언트를 설치할 때 node 버전을 확인해서 다운을 받아줘야한다. 나는 node 버전이 아폴로 클라이언트와 호환이 되지않아 버전 업그레이드를 했다 ㅠ

  2. 아폴로 클라이언트를 타입스크립트에서 읽을 수 있게 끔 yarn add -D @types/apollo-upload-client 한다.

//우선 _app.tsx or _app.js파일에 upload기능을 추가시켜줘야한다.

import {createUploadLink} from 'apollo-upload-clinet'

function MyApp({ Component, pageProps}) : AppProps (타입스크립트 타입 선언) {
	const uploadLink = createUploadLink({
	uri: 'http://backend.codebootcamp.co.kr/graphql'
    })
    
    const client = new ApolloClient({
	link : ApolloLink.from([uploadLink as unknown as ApolloLink]),
    cache: new InMemoryCache()
    })
    
    return (
		<ApolloProvider client = {client}>
      		<Component {...pageProps} />
		</ApolloProvider>
)
}

export default MyApp

import {gql, useMutation} from '@apollo/client'
import {useRef, useState} from 'react'


const ImagePage = () => {
  const [myImage, setMyImage] = useState('')
  const [file, setFile] = useState('')
  const fileRef = useRef<HTMLInputElement>(null)
  
  const onChangeFile = (event:any) => {
	const file = event.target.file[0] //배열로 묶여있기때문에 0번째를 불러와야한다.
    if(!file.type.includes('png')) {
		alert('png파일만 가능합니다')
      return
    }
	if (file.size < 5 * 1024 * 1024 // 5MB를 의미함) {
	const reader = new FileReader()
    reader.readAsDataURL(file)
    reader.onload= (event) => {
		if(event.target) setMyImage(String(event.target.result))
    }
  } else {
	alert('파일이 너무 큽니다 (5MB 제한!)')
    return 
  }
  setFile(file)
}

  const onClickImage = () => {
	fileRef.current.click()
  }
  
  const onClickImageFile = async() => {
	try{
	const result = await Promise.all([
      	uploadFileMutation({ variables: {file : file} }),
        uploadFileMutation({ variables: {file : file} }),
      	uploadFileMutation({ variables: {file : file} }),
      	uploadFileMutation({ variables: {file : file} }),
      ]) //이런식으로 쓰면 비효율적이니 map으로 받아서하는 것도 방법이다. new Array(4).fill(1).map(() => uploadFileMuation({variables: {file: file}})

  return (
    <div>
    	<button onClick = {onClickImage}> 이미지 </button>
		<input type ="file" ref = {fileRef} onChange = {onChangeFile style = {{display: 'none'}}
        />
//여기서 input type의 attribute에 display none을 준 이유는 file 형태의 input 고정 html&css을 javascript에서 제공해주기 때문에 원하는 형태로 변경을 하기가 힘들다.
//그렇기 때문에 버튼이미지를 만들어서 클릭했을 때 useRef 함수를 이용해서 input 태그에서 작동하는 모든 함수를 실행시킨다.
	<img src = {myImage} style = {{ width : '1000px', height: '600px'}} />
	<button onClick = {onClickImageFile}> 서버에 파일전송하기 </button>
</div>
)
}

export default ImagePage

결과물

간단해보이지만 굉장히 유용하게 쓰일 수 있는 방법같다. 피드백은 언제든지 Welcome입니다 !!!!!

profile
모든 코드에 의미를 담겠습니다.

0개의 댓글