CHZZK-Study / Grass-Diary-Client

취지직 2팀 프로젝트 Grass Diary
1 stars 3 forks source link

✨feat: 작성페이지 POST 오류 해결 및 이미지 업로드 기능 구현 #147

Closed rkdcodus closed 3 months ago

rkdcodus commented 3 months ago

✅ 체크리스트

📝 작업 상세 내용

1️⃣ 이미지 업로드 기능

일기 작성 post Api는 /api/diary/{memberId} 입니다. 새로 변경된 Api는 requestDto(json 값)와 image(이미지 파일)을 form-data로 body 에 담아서 보내야합니다.

reqeustDto 들어갈 내용

const requestDto = {
  content: quillContent,
  isPrivate,
  conditionLevel: `LEVEL_${moodValue}`,
  hashtags: hashArr,
  hasImage: hasImage,
};

경우에 따라 보내는 hasImage 값과 image 필드는 이러합니다.

상태 hasImage image 필드
이미지 있음 true 이미지 파일
이미지 없음 false ''

참고: Grass-Diary-Server #74

2️⃣ 작성한 이미지 전송 코드 설명

const formData = new FormData(); // FormData 생성
const [file, setFile] = useState(null); // 이미지 파일을 저장할 state
const [imageURL, setImageURL] = useState('');  
const [hasImage, setHasImage] = useState(false);

이미지 관련 state와 FormData 객체를 선언합니다.

// file 탐색기의 onchange 이벤트 발생 시 실행
const handleFileChange = e => {
    // 하나의 이미지 파일만 가져올 수 있음. 
  const file = e.target.files[0];
  const reader = new FileReader();
  reader.readAsDataURL(file);
  reader.onloadend = () => {
    setImageURL(reader.result);
  };
  setFile(file);
  setHasImage(true);
};

// html 코드
<form>
  <input type="file" onChange={handleFileChange} />
</form>
// image 미리보기
{imageURL && (
  <img
    {...stylex.props(CreateDiaryStyle.imageFile)}
    src={imageURL}
    alt="image file"
  />
)}

이미지 파일을 받는 input 창을 작성해주고 이미지를 골랐을 경우에만 미리보기로 이미지를 확인할 수 있습니다. input창에 파일 값이 생성되면 onChange 함수에서 file state에 이미지 파일 값을 업데이트 해줍니다.

작성 저장 버튼을 눌렀을 때 호출되는 handleSave 함수 내부에 작성된 이미지 관련 코드입니다.

if (file) {
      formData.append('image', file);
}

file 값이 null이 아니라면 formData에 image 이름으로 추가해줍니다.

requestDto 도 준비가 되었다면 formData에 추가해줍니다.

    formData.append(
      'requestDto',
      new Blob([JSON.stringify(requestDto)], {
        type: 'application/json',
      }),
    );

requestDto 의 content-type은 'application/json'으로 설정해줘야하기 때문에 Blob 객체를 이용하였습니다.

try {
      if (diaryId) {
        await API.patch(END_POINT.DIARY(diaryId), formData, {
          headers: {
            'Content-Type': 'multipart/form-data',
          },
        });
        ...
    };

그리고 image 파일과 reqeustDto가 들은 formData를 multipart/form-data 타입으로 보냅니다.

3️⃣ 기타 수정된 코드

requestBody

    const requestBody = {
      content: quillContent,
      isPrivate,
      conditionLevel: `LEVEL_${moodValue}`,
      hashtags: hashArr,
      month: month,
      date: date,
      day: day,
    };

🚨 버그 발생 이유 (선택 사항)

image 일기 작성에 500에러가 났었던 이유는 변경된 api로 보내주는 데이터가 맞지 않아서 났었던 오류입니다.

🔎 후속 작업 (선택 사항)

post 작업 후 patch 작업도 했는데 patch 과정에서 문제점이 있습니다.

  1. 이미지 + 글 작성 O
  2. 이미지 + 글 게시글에서 이미지만 변경 O
  3. 이미지 + 글 게시글에서 이미지와 글 변경 O
  4. 이미지 + 글 게시글에서 글만 변경 X → 이미지 삭제됨.
  5. 이미지 + 글 게시글에서 이미지 삭제 O→ 아직 구현 안했지만 글만 변경할 경우 이미지가 자동 삭제.

수정할 때 기존 이미지를 유지하고 싶을 경우( 글만 수정할 경우)에 이미지가 자동 삭제됩니다. 문제의 원인은 수정페이지에서 가지고 있는 이미지 주소는 S3 url 형식으로 되어있는데 patch는 post와 마찬가지로 이미지 파일을 보낼 땐, 파일 객체을 받도록 되어있습니다. 그래서 빈값으로 처리되어 기존 이미지가 삭제되는 것 같습니다. S3 url을 받을 수 있는지 확인해보았는데 S3 url은 받지 못하는 것 같습니다😥

🤔 질문 사항 (선택 사항)

📚 참고 자료 (선택 사항)

Blob 객체

Blob 객체는 binary large Object 로, 이진값의 큰 객체를 말하고 이미지, 비디오, 멀티 비디오 같은 멀티미디어 객체를 말합니다. 이미지는 서버에 보낼 때 Content-type = 'multipart/form-data’으로 설정해서 보내야합니다.

multipart/form-data 모든 문자들을 인코딩하지않음을 명시함. 이 방식은 <form>요소가 파일이나 이미지를 서버로 전송할때 주로 사용한다. 또한 요청시 POST로 보내야한다.

multipart가 왜 생겼냐면, 원래 HTTP request에 body에 보내고 싶은 데이터를 담아 보내는데 보내는 데이터의 타입을 Content-type을 통해 결정해줄 수 있습니다. 그리고 타입에 따라 서버에서 처리해줍니다. 근데 Content-type은 한가지 타입만 설정할 수 있습니다. 하지만 “이미지 + 이미지에 대한 설명” 처럼 다중 데이터를 보내고 싶을 때를 위해 multipart 타입이 생겨나게되었습니다.

이번 경우도 multipart/form-data로 보내야하는데 requestDto는 application/json 타입을 가져야하므로 Blob 객체를 이용했습니다. Blob 객체는 배열과 타입을 넣어 객체를 생성합니다.

formData에 파일 객체 함께 넣기 JavaScript-FromData-알아보고-서버로-이미지-전송해보자

📸 스크린샷 (선택 사항)

image image

✅ 셀프 체크리스트

이슈 번호: Close #146