JAVA/SpringBoot

게시판 프로젝트 - 첨부파일 다운로드

whyHbr 2023. 11. 16. 00:09
728x90
반응형

첨부파일을 다운로드 하는 기능을 구현할 것

 

-FileMapper 인터페이스 , 첨부파일 상세정보 조회 메서드 추가하기

첨부파일 다운로드는 게시글 상세 페이지에서 파일명을 클릭했을 때 실행된다. 이때 파일의 id(Pk)를 컨트롤러로 전달해 다운로드할 첨부파일의 상세정보를 DB에서 조회해야 한다.

 

DB에서 첨부파일 상세정보를 조회할 수 있도록 FileMapper 인터페이승[ 아래 메서드를 추가한다.

    /**
     * 파일 상세정보 조회
     * @param id - PK
     * @return 파일 상세정보
     */
    FileResponse findById(Long id);

 

-FileMapper XML 첨부파일 상세정보 조회 SQL쿼리 작성

앞에서 추가한 메서드와 연결할 파일 상세정보 조회쿼리를 작성한다.

<!-- 파일 상세정보 조회 -->
<select id="findById" parameterType="long" resultType="com.study.domain.file.FileResponse">
SELECT
<include refid="fileColumns" />
FROM
tb_file
WHERE
delete_yn = 0
AND id = #{value}
</select>

 

 

 

-FileService 클래스 , 첨부파일 상세조회 메서드 추가하기

쿼리 실행 결과를 리턴해준다

/**
* 파일 상세정보 조회
* @param id - PK
* @return 파일 상세정보
*/
public FileResponse findFileById(final Long id) {
return fileMapper.findById(id);
}

 

 

-FileUtils클래스, 첨부파일(리소스) 조회 메서드 추가하기

Resource는 inputStreamSource를 상속하며 리소스에 대한 접근을 추상화하여 사용된다. 이 Resource를 사용해 파일 다운로드를 처리한다.

DB에서 조회한 첨부파일 정보를 이용해 디스크상에 업로드된 물리적 파일 객체를 읽어들이는 기능을 구현해야 한다. 

FilesUtils에 아래 매서드를 추가한다.

/**
* 다운로드할 첨부파일(리소스) 조회 (as Resource)
* @param file - 첨부파일 상세정보
* @return 첨부파일(리소스)
*/
public Resource readFileAsResource(final FileResponse file) {
String uploadedDate = file.getCreatedDate().toLocalDate().format(DateTimeFormatter.ofPattern("yyMMdd"));
String filename = file.getSaveName();
Path filePath = Paths.get(uploadPath, uploadedDate, filename);
try {
Resource resource = new UrlResource(filePath.toUri());
if (resource.exists() == false || resource.isFile() == false) {
throw new RuntimeException("file not found : " + filePath.toString());
}
return resource;
} catch (MalformedURLException e) {
throw new RuntimeException("file not found : " + filePath.toString());
}
}

 

file : DB로 조회한 첨부파일의 상세 정보를 의미한다. 이를 통해 업로드된 첨부파일의 경로를 찾아낼 수 있다.

uploadDate: 첨부파일이 업로드된 날짜를 의미한다.

filename: 디스크에 업로드된 파일명을 의미

resource: 본 메서드에서 핵심이되는 객체이다. resource의 구현체인 UrlResource의 생성자 파라미터로 filePath에 담긴 첨부파일의 경로를 전달해 Resource객체를 생성한다.

만약 리소스가 없거나 리소스 타입의 파일이 아닌 경우 예외를 실행해 로직을 종료한다

 

-FileApiController클래스 - 첨부파일 다운로드 메서드 추가하기

FileApiController에 FileUtils를 멤버로 선언하고 downloadFile()메서드를 추가한다

        package com.study.domain.file;

        import com.study.common.file.FileUtils;
        import lombok.RequiredArgsConstructor;
        import org.springframework.core.io.Resource;
        import org.springframework.http.HttpHeaders;
        import org.springframework.http.MediaType;
        import org.springframework.http.ResponseEntity;
        import org.springframework.web.bind.annotation.GetMapping;
        import org.springframework.web.bind.annotation.PathVariable;
        import org.springframework.web.bind.annotation.RestController;

        import java.io.UnsupportedEncodingException;
        import java.net.URLEncoder;
        import java.util.List;

        @RestController
        @RequiredArgsConstructor
        public class FileApiController {

        private final FileService fileService;
        private final FileUtils fileUtils;

        // 파일 리스트 조회
        @GetMapping("/posts/{postId}/files")
        public List<FileResponse> findAllFileByPostId(@PathVariable final Long postId) {
return fileService.findAllFileByPostId(postId);
}

// 첨부파일 다운로드
@GetMapping("/posts/{postId}/files/{fileId}/download")
public ResponseEntity<Resource> downloadFile(@PathVariable final Long postId, @PathVariable final Long fileId) {
    FileResponse file = fileService.findFileById(fileId);
    Resource resource = fileUtils.readFileAsResource(file);
    try {
    String filename = URLEncoder.encode(file.getOriginalName(), "UTF-8");
    return ResponseEntity.ok()
    .contentType(MediaType.APPLICATION_OCTET_STREAM)
    .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; fileName=\"" + filename + "\";")
    .header(HttpHeaders.CONTENT_LENGTH, file.getSize() + "")
    .body(resource);

    } catch (UnsupportedEncodingException e) {
    throw new RuntimeException("filename encoding failed : " + file.getOriginalName());
    }
    }

    }

 

postid: 게시글 번호 PK를 의미

fileId: 첨부파일 번호PK를 의미한다

file: DB에서 조회한 첨부파일 상세정보를 의미한다

resource: FileUtils의 readFileResource()로 첨부파일 상세정보를 전달해 다운로드할 첨부파일을 리소스타입으로 조회한다

filename: 다운로드 할 첨부파일의 이름을 의미한다. 리소스를 읽어들일땐 실제로 디스크에 저장된 파일명 (saveName)을 통해 접근했지만 다운로드되는 첨부파일의 이름은 원본 파일명이되어야 한다.

ResponseEntity: REST API 방식에 사용되는 클래스로 이 클래를 이용하여 사용자의 HTTP요청에 대한 응답데이터를 개발자가 직접 제어할 수 있다.

 

-view.html - findAllFile() 함수에 파일 다운로드 링크 추가

// 전체 파일 조회
    function findAllFile() {

    ...
    ...
    ...

    // 3. 파일 영역 추가
    let fileHtml = '<div class="file_down"><div class="cont">';
response.forEach(row => {
fileHtml += `<a href="/posts/${postId}/files/${row.id}/download"><span class="icons"><i class="fas fa-folder-open"></i></span>${row.originalName}</a>`;
})
fileHtml += '</div></div>';

    // 4. 파일 HTML 렌더링
    document.getElementById('files').innerHTML = fileHtml;
    }
728x90