본문으로 바로가기

 

이전 게시글에서 파일을 업로드했다면, 이번에는 업로드된 파일을 다운로드하는 기능을 만들 것이다. 생각보다 복잡하지 않아서 개발하기 좋았다. 아래는 파일 다운로드가 진행될 순서이다. 참고해서 작업을 진행하면 된다.

1. 클라이언트에서 서버에 파일 다운로드 요청
2. 서버에서 다운로드할 파일 정보를 DB에 요청
3. DB에서 파일 정보 검색 후 서버로 전달
4. 서버에서 전달받은 정보로 파일 저장 경로에 있는 파일을 가져옴
5. 가져온 파일 데이터를 클라이언트에 전송하여 다운로드

 

1. 파일 다운로드 요청

- boardDetail.jsp

1
2
3
4
5
<div class="file-info">
    <span class="glyphicon glyphicon-camera" aria-hidden="true"></span>
    <a href='<c:url value="/filedownload?IDX=${file.IDX }"/>'>${file.ORG_FILE_NAME }</a>
    <span>${file.FILE_SIZE }kb</span>
</div>
cs

- 3행: 파일의 아이디를 같이 넘겨 다운로드 요청을 한다.

 

2. 파일 정보 가져오기

클라이언트에서 서버로 전달한 정보를 이용해서 다운로드할 파일의 정보를 가져오자. 이 부분은 이제껏 많이 작업해왔던 부분이기 때문에 따로 설명은 하지 않을 것이다.

- FileService.java

1
2
3
4
5
public interface FileService {
    
    Map<String, Object> selectFile(Map<String, Object> map);
 
}
cs

- FileServiceImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
@Service("fileService")
public class FileServiceImpl implements FileService {
    
    @Resource(name="fileDao")
    private FileDAO fileDao;
 
    @Override
    public Map<String, Object> selectFile(Map<String, Object> map) {
        return fileDao.selectFileInfo(map);
    }
 
}
cs

- FileDAO.java

1
2
3
4
5
6
7
8
9
@Repository("fileDao")
public class FileDAO extends AbstractDAO{
    
    @SuppressWarnings("unchecked")
    public Map<String, Object> selectFileInfo(Map<String, Object> map) {
        return (Map<String, Object>) selectOne("file.selectFileInfo", map);
    }
 
}
cs

- file_sql.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
    <select id="selectFileInfo" parameterType="hashmap" resultType="hashmap">
     <![CDATA[
         SELECT
             IDX,
             BOARD_IDX,
             ORG_FILE_NAME,
             SAVE_FILE_NAME
         FROM
             tb_file
         WHERE
             IDX = #{ IDX}
     ]]>
    </select>
cs

 

3. 클라이언트로 파일 전송

다운로드할 파일의 정보를 가져와서 클라이언트로 파일을 전송하는 작업을 해보자.

FileController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.tody.board.controller;
 
import java.io.File;
import java.net.URLEncoder;
import java.util.Map;
 
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import org.apache.commons.io.FileUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
 
import com.tody.board.service.FileService;
import com.tody.common.common.CommandMap;
 
@Controller
public class FileController {
    
    @Resource(name="uploadPath")
    String uploadPath;
    
    @Resource(name="fileService")
    private FileService fileService;
    
    @RequestMapping(value="/filedownload")
    public void downloadFile(CommandMap commandMap, HttpServletRequest request, HttpServletResponse response) throws Exception {
        
        Map<String, Object> file = fileService.selectFile(commandMap.getMap());
 
            String saveFileName = (String)file.get("SAVE_FILE_NAME");
            String originalFileName = (String)file.get("ORG_FILE_NAME");
            
            File downloadFile = new File(uploadPath + saveFileName);
            
            byte fileByte[] = FileUtils.readFileToByteArray(downloadFile);
            
            response.setContentType("application/octet-stream");
            response.setContentLength(fileByte.length);
            
            response.setHeader("Content-Disposition""attachment; fileName=\"" + URLEncoder.encode(originalFileName,"UTF-8"+"\";");
            response.setHeader("Content-Transfer-Encoding""binary");
            
            response.getOutputStream().write(fileByte);
            response.getOutputStream().flush();
            response.getOutputStream().close();
        
    }
    
}
 
cs

- 28행: 서버에서 화면으로 응답을 하기 위해 HttpServletResponse 를 사용한다.

- 30행: 클라이언트로부터 전달받은 정보로 DB에서 다운로드할 파일 정보를 가져온다.

- 35행: 파일을 업로드할 때 저장한 파일 이름을 가지고 디렉토리를 가져온다.

- 37행: 파일을 byte 배열로 변환한다.

- 39행: "application/octet-stream" 은 자바에서 사용하는 파일 다운로드 응답 형식으로, 어플리케이션 파일이 리턴된다고 설정한다.

- 40행: 파일 사이즈를 지정한다.

- 42행: "attachment;fileName="을 사용하면 다운로드시 파일 이름을 지정해줄 수 있다.

- 43행: "application/octet-stream"은 binary 데이터이기 때문에 binary로 인코딩해준다.

- 45행: 버퍼에 파일을 담아 스트림으로 출력한다.

- 46행: 버퍼에 저장된 내용을 클라이언트로 전송하고 버퍼를 비운다.

- 47행: 출력 스트림을 종료한다. 참고로 close() 함수 자체에서 flush() 함수를 호출하기 때문에 굳이 flush() 를 호출하지 않아도 된다.

 

 

파일을 클라이언트로 전송하는 방법외엔 전부 여지껏 작업해왔던 부분이라서 쉽게 다운로드를 구현할 수 있다. 만들다보니, "전체 다운로드" 기능도 만들면 좋지 않을까? 라는 생각도 들었다. 작업할 지 모르겠지만 만약 만들게 된다면 아래에 추가하도록 하겠다.
이제 정말 마지막으로 게시글을 수정/삭제할 때 파일도 수정/삭제 할 수 있도록 작업만 하면 파일 업/다운로드는 진짜 끝이 난다! 와!