마크다운 게시판에 드래그 앤 드랍으로 이미지 올리기 API 편 - 플리맨 프로젝트

김철기·2023년 3월 27일
6

플리맨(FleaMan)

목록 보기
14/14
post-thumbnail

해당 포스팅은 플리맨(FleaMan) 프로젝트의 광고 겸 개발일지입니다.
시간되실때 플리맨(https://fleaman.shop) 서비스 한번 사용해주시고 피드백주시면 감사하겠습니다.

이전 포스팅

이전의 이전 포스팅부터 마크다운 게시판과 관련된 컨텐츠를 포스팅하고 있다.

이번 포스팅에서는 업로드 하려는 이미지를 서버에 저장한 뒤 이미지 경로를 반환하는 API에 관련된 포스팅을 하겠다.

API는 뭘로 만들까?

플리맨 프로젝트 시리즈를 쭉~ 봐온 분들이면 우리가 python 언어 기반의 FastAPI 프레임워크를 사용하고 있다는 것을 알 것이다. 그런데 해당 포스팅을 처음으로 접한 분들도 계실 것 같아 다시 한번 언급한다. 우리는 빠르고 쉬운 FastAPI를 이용해 구축한 API 서버를 사용한다. 혹시 python이 아닌 다른 언어 기반의 API 구축을 목표로 하고 있다면 다른 포스팅을 구글링해서 참고하길 바란다.

라우터

라우터는 클라이언트에서 이미지를 업로드하고 싶을 때 API 서버로 요청을 보낼 path와 입력 데이터 형식에 대해 정의하는 부분이다.

from fastapi import APIRouter, UploadFile, File

@router.post('/upload-image')
def upload_image(
        file: UploadFile = File(...)
):
    '''
    이미지 업로드
    :param file:
    :return:
    '''

    return upload_image_logic(file=file)

우선 API 메소드는 데이터를 저장 요청하는 것이기 때문에 POST로 하였다. 그리고 요청 경로는 upload-image로 정했다. 해당 API로 요청을 하기 위해서는 'API서버 도메인/upload-image' 로 요청을 하면 되는 것이다.
해당 라우터는 file을 입력 받는데 우리가 업로드하려는 이미지이다. UploadFile, File은 fastapi 라이브러리에서 제공하는 데이터 형식으로 자세한 내용은 fastapi 문서를 참고하길 바란다.
마지막으로 upload_image_logic 함수를 실행하고 함수에서 return한 값을 클라이언트로 전송한다.

비즈니스 로직

위에서 언급한 upload_imgae_logic 함수에 대한 내용이다.

from datetime import datetime
import secrets
import boto3
from os import environ

s3 = boto3.resource('s3',
    endpoint_url='R2 엔드포인트',
    aws_access_key_id='R2 ID 값',
    aws_secret_access_key='R2 Secret 값'
)

def upload_image_logic(file):
    currentTime = datetime.now().strftime("%Y%m%d%H%M%S")
    saved_file_name = ''.join([currentTime, secrets.token_hex(16)])
    obj = s3.Object(
        bucket_name="버킷명",
        key=f"{saved_file_name}",
    )
    obj.put(Body=file.file)
    return saved_file_name

비즈니스 로직이라고 해서 뭐 대단한거 없다. 그냥 내가 저장하고 싶은 곳에다가 이미지를 저장하고 이미지 경로를 반환하는 함수다.
플리맨에서는 클라우드플레어에서 제공하는 R2 스토리지를 사용해서 스토리지에 이미지를 저장한다. (참고로 R2 스토리지는 AWS S3와 호환이 가능하다)
이미지명이 겹치면 안되기 때문에 생성시점과 해쉬값으로 고유한 이미지명을 부여한뒤 S3 라이브러리를 활용해 R2 스토리지에 저장한다. 마지막으로 저장된 파일명을 반환해주면 끝이다. 나는 기본적으로 스토리지의 url을 알고 있기때문에 파일명만 반환하고 있다. 혹시 url까지 전부 반환하고 싶다면 그렇게 해주면 된다.

근데 클라이언트에서는?

이것까지 소개하면 너무 길어질것 같아서 고민을 많이 했다. 그런데 아무래도 포스팅의 목적이 쉽게 정보를 공유하는 것이다보니 혹시라도 어려움을 겪는 분들이 계실까봐 소개하기로 결정했다.
클라이언트에서는 우리가 매번 사용하는 방식으로 API를 요청해주면 된다.

onDrop={(files, event) => {
              console.log('onDrop!', files, event)
              console.log(files[0])
              const formdata = new FormData();
              formdata.append(
                "file",
                files[0],
              )
              const headers={'Content-Type': files[0].type}
              if (files[0].size >= 5000000){
                alert("5MB 이상 파일은 업로드가 불가능합니다.")  
              }
              else if (files[0].type == 'image/png' || files[0].type == 'image/jpeg' || files[0].type == 'image/jpg' ){
                axios.post("API 서버도메인/upload-image", 
                formdata, headers)
                .then(function (response) {
                  let imageName = response.data
                  
                  let newValue = value + "\n\n !["+ files[0].name +"](https://이미지 스토리지 경로/"+ imageName + ")"
                  setValue(newValue)
                });
              }

onDrop 이벤트가 발생하면 파일의 용량을 한 차례 확인한다. 약 5MB보다 큰 파일은 비용적으로 부담이 되기 때문에 업로드 되지 못하게 막아놓았다. 만약 용량 조건을 만족하고 파일 형식이 이미지형식이라면 API 요청이 시작된다. 데이터를 파라미터로하여 API를 호출하면 API에서 이미지를 저장한 뒤 이미지명을 반환한다. 반환된 이미지명과 스토리지 경로를 더해서 최종 이미지 경로를 만들고 본문의 마지막에 마크다운 형식으로 추가되도록 해준다. 그러면 끝!

정상적으로 API가 동작하면 아래 이미지처럼 마크다운 형식의 이미지 태그가 추가되고 미리보기 화면에서는 해당 이미지가 표시된다.

마무리

오늘은 마크다운 게시판에서 이미지를 업로드할 때 동작하는 API에 대해서 알아보았다. 아주 심플하게 작성해놓았는데 실제로는 좀 더 복잡하다. 비즈니스 로직에서 이미지 파일을 압축하거나 유효성 검사를 하는 로직이 좀 더 있는데, 이건 각자의 컨텐츠에 맞게 필요하면 직접 추가하길 바란다. 클라이언트 부분도 마찬가지다 이미지 형식을 좀 더 제한하거나 용량을 제한하는 등 부수적인 부분은 필요에 따라 수정하길 바란다.
조금 걸리는 것이 클라우드플레어 R2부분의 설명이 누락된 것인데.. 혹시 소개가 필요하다면 댓글을 남겨주길 바란다. (어려운 것은 아니지만 클라우드 환경을 주로 사용하지 않는다면 낯설수 있으니!) 만약 R2 또는 S3와 같은 클라우드 스토리지를 사용하지 않는다면 그냥 서버에 저장할수도 있다. 서비스 목적이 아니라 스터디 목적이라면 비용이 발생하지 않도록 서버에 저장해보는것도 좋은 선택이다.

요즘 이런저런 일들이 많아 포스팅을 너무 오래 쉬었다. 조금이라도 여유 있을때 부지런히 작성해서 도움이 되도록 해보겠다.

profile
Deepveloper, deeplol.gg, fleaman.shop

0개의 댓글