Spring

[Spring + Javascript] fetch()를 활용한 api방식 웹페이지 파일처리 (4)

jongh0 2025. 6. 23. 20:24

해당 게시물에서는 자바스크립트 fetch() 함수를 통한 비동기 api 웹페이지 파일처리 방식에 대해서 알아보겠다.

 

 

[페이지 포워딩을 위한 BoardController 클래스]

 

BoardController

 해당 클래스에서는 board/insert 그리고 board/detail/*에 대해서 페이지 포워딩을 진행하는 클래스이다.

 

게시물 등록을 위한 페이지는 board/insert 요청이 들어왔을때 insert.jsp를 포워딩해줄 것이고,

게시물 상세페이지는 board/detail/* 요청이 들어왔을때 detail.jsp를 포워딩 해준다.

 

일단 게시물 내용과 파일등록을 위한 jsp 파일코드를 살펴보자.

[게시물 내용과 파일 첨부를 위한 insert.jsp]

 

해당 코드에서는 브라우저에서 서버로 데이터를 보내기 위한 형식의 태그가 존재한다.

제목과 내용 은 이전과 동일하게 input 태그와 textarea를 통해서 입력을 받고, 게시글 작성 버튼에 대해서는 button type을 통해서 onclick 속성에 insertBoard() 메서드를 설정하여 자바스크립트 fetch 함수를 통해서 비동기 처리를 할 예정이다.

(그래서 form 태그에는 주석이 달려있음)

 

해당 글에서 가장 중요한 파일첨부에 대한 태그는 input 태그에 type속성으로 file로 설정하면 브라우저에서 파일 첨부가 가능하도록 해준다. 파일 첨부 태그에 대해서는 네임을 "f"로 설정하였다.

 

 

 

[insertBoard() 메서드가 담긴 자바스크립트 코드 insert.js]

 

사용자가 jsp파일을 포워딩 받은 상태에서 제목,내용,파일첨부하고 게시물 버튼을 누르게 되었을때 onclick 속성으로 실행되는 insertBoard() 메서드이다. 해당 메서드에서는 document.querySelector() 를 통해서 element를 참조하고 제목과 내용에 대해서는 .value를 통해서 값을 가져온다. 그리고 첨부된 파일에대해서는 fileTag라는 변수에 파일이 담기는 input 요소를 담고

files[0]을 통해서 files에 어떤 파일이 담겼는지 받아낸다. 해당 게시물에서는 파일 하나만 선택되도록 구현하였으므로

다음과 같이 진행한다. 

 

 

 만약 insert.jsp에 파일을 담는 input 태그에 옵션으로 multiple을 설정하였다면 사용자는 여러 파일을 선택해서 담을 수 있으며변수 files는 배열형태로 받아진다.

 

파일을 담아주기 위해서 이제는 단순한 객체가 아닌 new FormData(); 를 통해 fd 객체를 생성하고

fd객체에 append 메서드를 통해서 (key, value) 형식으로 객체에 값을 담아줄 수 있다. FormData()로 생성된 객체는 form 태그 없이도 form과 같은 역할을 해주어 서버에서 요청처리 메서드의 파라미터에 적는 @RequestBody를 적어주지 않고 데이터를 받아올 수 있다.

 

해당 코드에서는 파일이 단순히 하나만 처리하도록 되어있어서 다음과 같이 파일에 대해서 한번만 append() 메서드를 사용하지만,

 

아까 말했던 여러 파일을 다뤄야 하는 경우에는 다음 코드와 같이 for of문을 통해서 배열 files 에 대해서 file 하나씩 꺼내서 

fd에 append() 메서드를 사용해주어야 한다. 그러면 당연히 f에 대해서 서버에서 리스트와 같은 형태로 해당 데이터를 받아야 한다.

 

다시 돌아와서, fetch 함수에 파라미터로 들어가는 url과 option으로는 api/board 요청에 대해서 POST 방식으로 바디에 사용자의 입력과 파일이 담긴 fd 설정하고 fetch 비동기 함수를 실행한다.

 

[fetch()함수를 통한 요청을 처리하는 BoardApiController]

 

fetch에서 게시물 작성을 위해서 POST방식의 api/board 요청을 처리하는 insert() 메서드이다.

 

@PostMapping 어노테이션을 통해서 insert() 메서드가 작동하도록하고, @RestController를 통해서 해당 클래스가 Controller임과 다시 자바스크립트로 응답을 보낼 때, 페이지 포워딩이 아닌 데이터(값)을 그대로 보내기 위해서 사용하였다.

 

@RequiredArgsconstructor 어노테이션을 통해서 의존성주입(DI)을 위한 생성자를 자동으로 실행하도록 하였다.

 

그리고 insert()  메서드 파라미터에 담긴 BoardVo vo와 MultipartFile f는 fetch() 메서드를 통해서 요청이 보내질 때 담긴 fd로 부터 값이 받아와진다.

 

 

 

dirPath : 사용자가 업로드한 파일이 서버측 디렉터리 구조에 어디에 저장될지에 대한 경로 문자열을 담고있는 변수

random : 고유한 파일명으로 설정하기 위한 System메서드와 UUID메서드 사용 (동일한 파일이 있다면 덮어쓰기되는 문제를 방지)

originalFilename : file이 담긴 f를 통해서 getOriginalFilename() 메서드를 사용하게 되면, 사용자가 파일을 첨부했을 때의 파일명 그대로를 담은 변수

 

ext : 사용자가 담은 파일의 확장명을 구분하기 위한 변수로 xxxx.png 라고 할 때, split메서드를 통해서 배열로 나누고 맨 마지막 인덱스 ( 확장자명)을 담고있는 변수

 

changeName : 설정한 난수와 확장자 명을 합쳐서 만든 고유 파일명

originalFilename : 사용자가 파일을 업로드 할 때의 파일명

 

new File() 생성자를 통해서 생성자 파라미터로 dirPath(절대경로) + changeName(고유파일명)과 같이 설정하면

해당 경로에 고유한 파일을 생성하고 해당 파일을 가르키는 객체를 생성한다. 

 

그리고 사용자가 첨부한  파일 f에 .transferTo() 메서드로 targetFile을 담아주게 되면 비어있던 파일에 내용이 담기게 된다.

 

 

 

 

vo에 따로 입력해주지 않은 오지리널 파일명이나 변환파일명을 setter를 통해서 입력해주고 로그인은 구현하지 않았으므로 임의의 작성자 번호를 따로 설정해주었다. 해당 vo객체를 통해서 게시물 등록 쿼리문을 실행하여 게시물 내용을 등록해주고 결과를 result로 반환한다. 쿼리문 결과 그리고 결과에 대한 메세지를 담아주기위해서 Hashmap을 통해서 두 데이터 모두 data와 status로 담아주고 map을 js로 응답한다.

 

 

 json() 메서드를 통해서 서버로 부터 받은 resp에 대해 데이터파싱을 통해 map형태로 되돌려 놓고 해당 map.data를 통해서 정상적으로 쿼리문이 실행되었는지 확인하고 정상적으로 실행되었다면 map.status를 알림창으로 띄워주고 오류가 있다면 .catch을 통해서 처리하도록 하였다.

 

 

 board/insert 요청을 통해 리다이렉트 된 게시글 작성 페이지에서 제목과 내용 그리고 파일을 선택해서 게시글을 작성하게 되면

위에서 설명한 insert() 로직이 모두 작동하게 된다. 올바르게 쿼리문이 작동했음을 알려주는 알림창이 뜨게되고 아까 설정한 경로에 고유한 이름을 가지도록 설정한 랜덤파일명으로 파일이 서버(스프링)측 디렉토리에 저장되었을 것이다.

 

DB에 담긴 게시물 데이터

 

경로에 저장된 파일

 

String random = System.currentTimeMillis() + "_" +UUID.randomUUID().toString();

 

다음으로 설정한 시간값, 랜덤한 문자를 통해서 만들어진 이름과 사용자가 업로드한 파일과 동일한 확장명으로 파일이 저장된 것을 확인할 수 있다.

 

이제 해당 파일을 상세페이지에서 보여주도록 포워딩 해주어야 한다.

상세페이지를 포워딩해주고, js파일을 바로 실행시키면서 fetch() 함수를 통해서 db에 포워딩된 페이지 글번호 를 통해 db를 참조하여 어떤 제목, 내용 그리고 어떤 파일이 담겨있는지 페이지를 채워주면 된다.

 

BoardController detail()

 

일단 어떤 board/detail/*로 @RequestMapping 어노테이션에 파라미터를 설정하여 *부분에 어떤 글번호가 오더라도 일단 detail 페이지.jsp를 포워딩하도록 작성하였다.

 

BoardApiController detail()

 

@RequestMapping으로 해당 ApiController 클래스 전체가 요청 api/board 키워드로 묶여있으므로, 해당 detail 메서드가 실행되기 위해서는 메서드 파라미터에 적힌 경로변수 참조 어노테이션으로 api/board/${no} 라는 요청이 왔을때 해당 메서드가 실행되고 no가 해당 메서드에 사용된다. 동일하게 no라는 글번호가 어떤 내용을 담고있는지 Select 쿼리문을 실행하게 되고

결과로 글 전체 내용을 담고있는 BoardVo 객체 vo와 결과상태를 나타내는 두가지 값을 map을 통해서 반환하였다.

 

 

detail.jsp

 

예전 글에서 상세보기 페이지를 만든것과 동일하다 해당 js코드에서는 detail이 포워딩 됨과 동시에 함수가 실행될 수 있도록 맨 아래 selectBoardOne(no)를 호출하며, no는 url에 맨 마지막에 기입하려는 경로변수에 오는 글번호를 서버에 전달한다.

해당 글번호를 통해서 아까 BoardApiController에 있는 Select 쿼리를 통해 글정보를 꺼내오고 응답받은 map.data에 해당 객체가 담기게 된다.

 

서버측에 static 하위 폴더에 upload 폴더에 저장되었던 사용자 업로드 파일을 img 태그를 통해서 src 속성을 통해 경로를 설정해 주면 된다. 경로는 절대경로가 아니므로 original 경로 필드를 사용하는 것이 아니라, 8080이라는 스프링 서버를 기준으로 어느 폴더에 어떤 파일명과 확장자로 존재하는지를 적어주기 위해

 

다음과 같이 적어줄 수 있다. 스프링에서 resources폴더 아래 static 폴더가 정적파일이 들어있는 폴더 기준이 되므로 하위 update폴더 내에 파일명과 확장자를 기입해둔 필드 data.cahageName을 EL로 참조하면 된다.

 

서버 내부디렉토리를 통해서 이미지를 출력

 

다음과 같이 올바르게 이미지 경로가 img 태그 내에 src 속성값으로 설정되어 이미지가 출력되는 결과를 볼 수 잇다.