티스토리 뷰

요즘 sns 프로젝트를 진행하고 있다. 유저가 포스트에 사진을 첨부해 업로드하면 DB에 저장하는 방식으로 진행하려고 했다. 백엔드 팀원도 나도 처음 구현해보는 기능이라 생각처럼 안되서 사진 저장 서비스를 이용하기로 했다. 전에 한 번 사용해본 사진 이미지 크기를 조정해서 저장할 수 있는 Cloudinary를 이용하기로 했다.

 

cloudinary npm 패키지도 있지만 REST API를 사용해서 추가로 설치하지는 않았다. cloudinary를 가입하면 받는 cloud name, API key등은 .env 파일로 관리했다.

 

설정에 들어가서 upload에 Upload presets를 만들어줘야한다. 기본으로 signed로 되어있는데 그럼 업로드마다 로그인을 해줘야 하므로 로그인을 하지 않아도 되는 Unsigned로 프리셋을 추가해 준다. 설정 시에 Folder에 폴더이름도 지정해주면 그 위치에 저장된다. 프리셋을 추가하면 name이 생기는데 이걸 업로드할때 사용하면 된다.

 

사진 파일을 한개씩 올릴 수도 있지만 나는 최대 4개까지 한 번에 올릴 거라서 예시에 나와있는 코드를 참고했다.

// Upload multiple files using a form (unsigned)
const url = "https://api.cloudinary.com/v1_1/demo/image/upload";
const form = document.querySelector("form");

form.addEventListener("submit", (e) => {
  e.preventDefault();

  const files = document.querySelector("[type=file]").files;
  const formData = new FormData();

  for (let i = 0; i < files.length; i++) {
    let file = files[i];
    formData.append("file", file);
    formData.append("upload_preset", "docs_upload_example_us_preset");

    fetch(url, {
      method: "POST",
      body: formData
    })
      .then((response) => {
        return response.text();
      })
      .then((data) => {
        document.getElementById("data").innerHTML += data;
      });
  }
});

 

예시의 url demo자리에 아까 있던 cloud name을 써서 업로드 url을 구성하면 된다.

const url = "https://api.cloudinary.com/v1_1/내클라우드이름/image/upload";

 

input으로 입력 받은 file들이 있는 photos라는 목록을 넘겨준다. FormData 객체를 생성해서 사진을 하나씩 반복문을 돌려가며 업로드해 준다. (한 번에 넣으려고 했더니 마지막으로 넣어진 사진 1장만 업로드되어서 예시대로 반복문을 사용했다.) 업로드를 할 때 signed로 설정하면 file, preset, api key, timestamp, signature가 필수 파라미터인데 나는 Unsigned라서 상관이 없었다. 그런데 그걸 모르고 한참을 signature 때문에 생기는 에러로 고생했다.... 🥲 업로드가 끝나고 나면 response에 url로 업로드된 이미지 url을 보내준다. 그리고 그 url을 서버에 저장했다. 자꾸 에러가 나서(signature 가만 안둬....) axios에서 fetch로 수정했는데 내일 다시 axios로 해봐야겠다. 

 

const uploadPhotos = (
  photos: PhotosType,
  setUploadImgUrls: setState
) => {
  const formData = new FormData();
  for (let i = 0; i < photos.length; i += 1) {
    formData.append('file', photos[i].file);
    formData.append(
      'upload_preset',
      import.meta.env.VITE_CLOUDINARY_PRESET_KEY
    );

    fetch(import.meta.env.VITE_CLOUDINARY_URL_UPLOAD, {
      method: 'POST',
      body: formData,
    })
      .then((res) => res.json())
      .then((res) => setUploadImgUrls((prev) => [...prev, res.url]));
  }
};

 

 

아무래도 통신 코드가 비동기이다보니 예상과 다른 작동 순서때문에 한참을 헤맸다.

- 예상

  1. 포스트 작성 버튼 클릭
  2. 이미지 업로드
  3. 업로드 후 url 반환
  4. 반환된 url을 배열형태로 가공해 자체 서버 포스트 작성 통신 코드 body에 추가
  5. 서버로 통신

- 동작

  1. 포스트 작성 버튼 클릭
  2. 이미지 업로드
  3. url이 없는 상태로 body에 담아서 자체 서버 포스트 작성 코드 통신 시작
  4. url은 빈배열로 서버에 도착
  5. 이미지 업로드 후 url 반환

 

결국에 useEffect를 사용해서 의존성배열에 uploadImgUrls를 넣고 input으로 전달받은 파일 목록인 photos와 업로드 후 생성되는 image url을 저장하는 uploadImgUrls의 길이가 같아지면 서버로 요청을 보냈다. 해결...! 😭

useEffect(() => {
    if (!!!uploadImgUrls.length) return;
    if (photos.length === uploadImgUrls.length) {
      createNewPost(textValue, categorys, navigate, type, uploadImgUrls);
    }
}, [uploadImgUrls]);

 

REST API 이미지 업로드 Cloudinary 공식 문서 링크

728x90
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함