vue + jpa project (19) - 이미지 보완 처리 본문

프로그램/Vue.js

vue + jpa project (19) - 이미지 보완 처리

반응형

이번 장에서는 기존에 구현이 되었던 이미지 처리에 대한 부분을 보완할 예정이다.

 

기존에 이미지를 첨부한 게시글의 상세화면으로 들어가보면 아래 이미지처럼 액박이 뜨고 

콘솔 로그에 403 Forbidden 오류가 발생한다. 

그 이유는 토큰처리를 위해서 모든 request 요청에 대해서 TokenRequestFilter 을 통과해서 처리되도록 변경이 되었고,

이를 통해서 토큰 값이 전달이 되어야 정상 동작을 하는데 기존 방식은 단순 url get 방식으로만 호출하기 때문에 

이런 현상이 발생하게 되었다.

 

이러한 부분을 해소하기 위해서는 어쩔 수 없이 기존 방식을 버리고 구현된 axios를 호출하도록 수정이 되어야 하고,

백엔드에서도 수정이 불가피하다. 

 

여기서 잠깐 백엔드에서 이미지를 가져오는 방법은 몇가지가 있다. 

a. url로 이미지를 호출하는 방식
b. 이미지가 있는 경로를 가져오는 방식
c. 이미지의 바이너리 값을 가져오는 방식

 

a. 번은 이미 기존에 적용되었던 것이지만 버려야하는 상황이다.

b. 번은 일반적으로 처리가 가능한 방법이다. 하지만 b번의 경우, 다른 서버나 해당 서버에서 Path를 지정할 수 없는 

    영역에 있다면 문제가 발생한다. 현재 파일업로드 경로도 프로젝트 폴더와 전혀 무관한 곳인 것도 그렇다.

따라서 이번에는 c. 번 방식으로 처리하려고 한다. 물론 이미지에 대한 특수한 경우에 한정적인 부분도 있으니 

이점 참고하기 바란다.

 

1. build.gradle 에 Base64 implementation

    백엔드에서 이미지 처리를 위해서 Base64 Codec 관련 모듈을 추가한다. 추가하고 refresh gradle을 실행한다.

// build.gradle
... 중략
implementation group: 'commons-codec', name: 'commons-codec', version: '1.15'

 

2. 백엔드 쪽 이미지 처리 방식 변경

    일단 기존에 호출했던 @GetMapping("/board/picture")  은 그대로 두고 다른 메소드를 추가해서 다른 이름으로 부여

    하겠다. 

// BoardController.java

    ... 중략
    @GetMapping("/board/preview")
    public String preview(@RequestParam("boardNo") Long boardNo) throws Exception{

         String pictureUrl = boardService.getPictureUrl(boardNo);

         try {
             ByteArrayOutputStream baos = new ByteArrayOutputStream();
             String formatName = pictureUrl.substring(pictureUrl.lastIndexOf(".") + 1);
             MediaType mediaType = getMediaType(formatName);
                if(mediaType != null) {
                    File file = new File(file_upload_path + pictureUrl);
                    BufferedImage image = ImageIO.read(file);
                    ImageIO.write(image, formatName, baos);
                    String previewImg = new String(Base64.encodeBase64(baos.toByteArray()));
                    return previewImg;
                }
            } catch(Exception e) {
                e.printStackTrace();
            }
        return null;
    }

    ... 중략
    private MediaType getMediaType(String formatName){
        if(formatName != null) {
            if(formatName.equalsIgnoreCase("JPG")) {
                return MediaType.IMAGE_JPEG;
            }
            if(formatName.equalsIgnoreCase("GIF")) {
                return MediaType.IMAGE_GIF;
            }
            if(formatName.equalsIgnoreCase("PNG")) {
                return MediaType.IMAGE_PNG;
            }
        }
        return null;
    }

처리 방식은 게시판 번호에 맞는 데이터를 읽어와서 거기에 있는 picture_url 값을 읽어온다. 

필드명이 url이지만 사실 파일명이다. 

 

가져온 파일명의 확장자가 jpg, gif, png 인 경우만 처리를 하기 위해서 메소드를 하나 더 추가했다.

파일경로와 파일명을 이용하여 파일을 읽어오고 읽어온 파일을 BufferedImage 로 변환하고, 

그것을 ByteArrayOutputStream 으로 쓴 뒤에 Base64 인코딩을 통해서 문자열로 변환한다. 

문자열로 변환 된 값을 프론트로 전달하게 된다.

 

 

3. 프론트 쪽 이미지 처리 방식 변경

    기존에 호출했던 방식은 아래와 같이 주석 처리하고 새로운 행을 추가한다. 

// BoardDetail.vue

    ... 중략
    <div class="board-content">
      <img v-if="picture_url" :src="previewPicture(`${boardNo}`)" width="220" height="220">
      <!-- img v-if="picture_url" :src="`${$serverUrl}/board/picture?boardNo=${boardNo}`" width="220" height="220" -->
    </div>
    ... 중략

 

그리고 중간에 데이터 변수 picture 를 하나 추가한다.

// BoardDetail.vue

    ... 중략
  data() {
    return {
      requestBody : this.$route.query,
      boardNo : this.$route.query.boardNo,

      title : '',
      content : '',
      writer : '',
      picture_url : '',
      picture : '',	// 추가됨
      reg_date : '',
    }
  }, 
    ... 중략

 

그리고 위에서 호출하는 이름과 동일하게 axios 를 처리하는 함수를 아래와 같이 추가한다.

// BoardDetail.vue

    ... 중략
    previewPicture(boardNo) {

      this.$axios.get(this.$serverUrl + '/board/preview?boardNo=' + boardNo + '&timestamp=' + new Date().getTime(), {})
        .then((res) => {

          this.picture = 'data:image/jpeg;base64, ' + res.data
          
        })
        .catch((err) => {
            console.log(err)
        })

        return this.picture
    },
    ... 중략

테스트를 위해 콘솔로 this.picture를 찍어보면

'data:image/jpeg;base64, /9j/4AAQSkZJRgABAgAAAQABAAD/2wBDAAg ... ' 식으로 찍힐 것이다.

 

소스를 모두 수정했다면 한번 실행시켜 보자. 

이미지처럼 깨지지 않고 잘 나온다.


하지만 여기서 끝이면 다행이지만 가끔 이상 동작을 할 경우도 있다.

지금은 정상적으로 작동을 하겠지만 이미지를 백엔드에서 불러오기도 전에 return this.picture 가 먼저

실행이 되어 버려서 액박이 뜨는 경우도 있다.

그래서 Promise를 이용하여 async/ await 를 이용하여 sync 방식으로 처리할 수도 있다.

아래는 메소드를 두개 더 추가하여 sync 방식으로 처리하였다. toSetImage 메소드에서 실제 백엔드를 호출하고 

받은 값을 resolve 를 이용해서 값을 리턴한다. 그리고 그 값을  호출한 메소드에서 then 영역에서 값을 꺼내고

그것을 실제 값에 부여하고 리턴한다.

// BoardDetail.vue

    ... 중략
    previewPicture2(boardNo) {
      this.toSetImage(boardNo)
      .then((res) => {
        this.picture = res
      })

      return this.picture
    },

    toSetImage(boardNo) {

      return new Promise(async (resolve, reject) => {

        await this.$axios.get(this.$serverUrl + '/board/preview?boardNo=' + boardNo + '&timestamp=' + new Date().getTime(), {})
          .then((res) => {

            resolve('data:image/jpeg;base64, ' + res.data)
            
          })
          .catch((err) => {
              console.log(err)
          })

        })
    },

    ... 중략

 

아직 나 자신이 Promise 사용에 대해서 명확히 아는 부분이 아니어서 일단 맞는지 여부는 조금 애매하지만

그래도 async/await 방식은 실제 개발에서 필요한 사항이고 흐름은 비슷하다.

해당 메소드를 호출하도록 수정하고 실행시켜 보면 역시 잘 나온다.

 

이상으로써 이장을 마무리 한다.

 

다음 장에서는 1:N 구조의 공통코드 관리 화면을 시작해보겠다.

반응형

프로그램/Vue.js Related Articles

MORE

Comments