Technology[DevOps]

Naver ObjectStorage를 이용하여 이미지 저장서버 활용하기(1)

Thunderland 2022. 3. 11. 11:51

앞선 게시글에서 사용자로부터 이미지파일을 받으면 이를 DB에 Blob형태로 저장하기, Static 이미지파일로 저장해서 Spring서버 자동재시작을 통해서 해당 이미지파일 인식하기 등 다양한 방법을 사용해봤지만 최근 자주 사용되는 별도의 이미지서버를 이용하여 구현하는 방법에 대해 알아보겠습니다.

 

(1) React (이미지파일 받아서 Spring에 전달하기)

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

function BoardWrite(props) {
//미리보기
  const [ImagetValue, setImageValue] = useState(null);
//Spring으로 전송할 파일
  const [loadImg, setLoadImg] = useState(null);
  
  const onImageChange = (e) => {
    const localImg = e.target.files[0];
    setLoadImg(localImg);

    let fd = new FileReader();
 
         if (localImg) {
             fd.readAsDataURL(localImg);
             console.log(fd);
         }
 
         fd.onloadend = () => {
             const previewImgUrl = fd.result;
 
             if (previewImgUrl) {
              setImageValue(previewImgUrl);
             }
         }
  };
  
const saveData = (event) => {
  
  event.preventDefault();

  console.log("num" + num);
  const userData = {
    post_num : num,
    post_title : TitleValue,
    post_text : ContentValue,
    post_date: dateString,
    post_topic : "자유",
    user_key : login.loginUserKey,
  }
  const file = new FormData();
  file.append("file", loadImg);
  file.append("userData", new Blob([JSON.stringify(userData)], {type: "application/json"}));
  console.log(loadImg);

  const updateURL = "http://digitalgamenomad.cf:8088/createBoard";
  axios
  .post(updateURL, file,
  {

  }
  ).then((response) => {
    console.log(response);
    dispatch( { type: "board_out", data: null } );
    alert("변경 완료!");
    window.location.replace("/BoardFree"); 
  })
  .catch(error => {
      alert("이미지파일은 1048576 byte 까지 업로드 가능합니다.");
  });

};

 return (
 <div className={styles.Register}>
      <h2 className={styles.r__h2}>글 작성하기</h2>
      <form className={styles.r__form} onSubmit>
 		<div className={styles.r__image}>
          <label className={styles.label}>이미지</label>

          <div className={styles.previewImg}>
            <img src={ImagetValue} />
          </div>

          <form encType='multipart/form-data'>
            <label htmlFor='file'>파일선택</label>
            <input type="file" className={styles.file} id="file" accept='image/*'
              onChange={onImageChange} /> </form>

          <button onClick={deleteImgHandler}>❌</button>
        </div>
        <Link to="/BoardFree">
        <button className={styles.r__submit} onClick={saveData}>
          저장하기
        </button>
        </Link>
       </form>
      <Switch>
        <Route path="/BoardFree" component={BoardFree} />
      </Switch>
</div>
);
}

export default BoardWrite;

위의 코드는 실제 프로젝트에서 일부 발췌한 것이므로 사용시 일부 수정해서 사용하시면 됩니다. 사용자가 파일선택 label을 누르면 htmlFor을 통해 id가 file인 input 태그가 실행되고 accept를 이용해 image 파일만 받을 수 있도록 지정하였습니다. 이후 onChange를 통해 파일이 변경되면 onImageChange 함수가 실행되고 사용자가 업로드한 파일 원본은 loadImg에 setter로 저장되고 이후 FileReader를 통해 해당 파일을 미리보기 할 수 있도록 변경해서 ImagetValue에 setter로 저장합니다. 이후 setter가 실행되었으므로 Re-rendering이 이루어지고 이때 img태그의 src에 ImagetValue를 넣어놔서 업로드한 파일의 미리보기 형식으로 화면에 출력합니다. 이후 사용자가 저장하기 버튼을 누르면 onClick을 통해 saveData 함수가 실행되고 해당 함수에서 userDate라는 객체로 이미지와 함께 보낼 데이터를 저장하고 FormData를 생성한 후 해당 file에 이미지파일인 loadImg와 앞서 생성했던 userData를 append한 후 이를 Spring의 서버로 axios를 통해 데이터를 전송합니다.

 

(2) Spring(전달받은 이미지파일을 Naver의 ObjectStorage 서버에 저장하기)

 

//게시글 작성
    @PostMapping(value = "/createBoard", consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE})
    public String createBoard(@RequestPart(required = true) HashMap<String,Object> userData, @RequestPart(required = false) MultipartFile file) throws IOException, SQLException, IllegalStateException {

        PostInfo postInfo = new PostInfo();
        postInfo.setPostTitle((String)userData.get("post_title"));
        postInfo.setPost_text((String)userData.get("post_text"));
        postInfo.setPost_date((String)userData.get("post_date"));
        postInfo.setPost_topic((String)userData.get("post_topic"));

        int user_key = (Integer)userData.get("user_key");
        Long long_user_key = Long.valueOf(user_key);
        UserInfo u1 = userInfoRepository.findByUserKey(long_user_key);
        postInfo.setUserKey(u1);

        postInfoRepository.save(postInfo);

       if (file != null) {

            final String endPoint = "https://kr.object.ncloudstorage.com";
            final String regionName = "kr-standard";
            final String accessKey = "accessKey";
            final String secretKey = "secretKey";

            // S3 client
            final AmazonS3 s3 = AmazonS3ClientBuilder.standard()
                    .withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(endPoint, regionName))
                    .withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials(accessKey, secretKey)))
                    .build();

            String bucketName = "bucketName";

            // prepare MultiPartfile Upload
            ObjectMetadata metadata = new ObjectMetadata();
            metadata.setContentType(MediaType.IMAGE_PNG_VALUE);
            metadata.setContentLength(file.getSize());

            //Random
            double dValue = Math.random() * 1000000000;
            long iValue = (long) (dValue);

            try {
                s3.putObject(new PutObjectRequest(bucketName, Long.toString(iValue), file.getInputStream(), metadata));
            } catch (AmazonS3Exception e) {
                e.printStackTrace();
            } catch (SdkClientException e) {
                e.printStackTrace();
            }

            ImageInfo imageInfo = new ImageInfo();
            imageInfo.setImage_url(Long.toString(iValue));
            imageInfo.setPostKey(postInfo);
            imageInfoRepository.save(imageInfo);
        }

        return "success";
    }

위의 코드는 실제 프로젝트에서 일부 발췌한 것이므로 사용시 일부 수정해서 사용하시면 됩니다. 이미지파일과 JSON파일을 받는 방식은 앞선 게시글과 동일합니다. 이미지파일을 저장하는 방식이 달라졌는데 큰 틀은 NaverCloud가 제공하는 JAVA SDK로 이미지를 업로드하는 방식을 가져왔습니다. file이 null이 아닐 경우 NaverCloud에서 accessKey와 secretKey를 복사해서 붙여넣고 bucketName에 기존에 생성해놓은 이미지파일을 저장할 버킷 이름을 입력합니다. 이후 MultipartFile을 저장하는 방식이 변경되었는데 네이버에서 제공하는 방식은 저장되어있는 Static 이미지파일을 저장하는 방식인 반면에 지금은 React에서 받아온 파일을 저장하는 방식이기에 저장되어있지 않은 MultipartFile을 저장해야합니다. 이를 위해 일반파일과 달리 MultipartFile은 파편화된 파일이므로 ObjectMetadata를 이용해 해당 MultipartFile의 contentType과 contentLength를 별도로 지정해주고 이를 putObject를 통해 해당 클라우드에 저장합니다. 이 때 파일이름은 Math.random()을 이용하여 9자리 난수로 지정하고 이를 DB에 저장하여 추후 해당 이미지파일에 접근할 때 해당 난수를 이용할 예정입니다.

 

(3)NaverCloud

accessKey / secretKey

 

bucketName

이번 게시글에서는 React에서 사용자로부터 이미지파일을 입력받아 이를 Spring에 전송하고 Spring에서 NaverCloud에 접근해서 이미지파일을 저장하고 Math.random()을 이용해 9자리 난수를 생성한 후 이를 이미지파일 이름으로 지정하고 DB에 저장하여 추후 해당 이미지파일에 접근할 때 사용할 수 있도록 세팅하는 방법에 대해 알아보았습니다.