본 내용은 혼자 공부하기 위하여 다른 포스트를 보면서 저에게 필요한 부분과 궁금했던 부분을 추가하여 게시하는 곳입니다.
궁금한 것에 대한 것은 모르는 것이 많겠지만 함께 알아보도록 노력하겠습니다.
참조 게시 포스트 : http://addio3305.tistory.com/
------------------------------------------------------------------------------------------------------------------------------------------
1. 파일 다운로드 화면
전에 작성한 업로드는 데이터베이스에 파일의 정보를 담아두고 서버에 파일을 저장하는 방식이다. 먼저 사용자에게 파일을 정보와 파일을 받을 수 있는 메뉴부터 만들어 보자.
1.1 Controller
SampleController.java
@RequestMapping(value="/sample/openBoardDetail.do") public ModelAndView openBoardDetail(CommandMap commandMap) throws Exception{ ModelAndView mv = new ModelAndView("/sample/boardDetail"); Map<String,Object> map = sampleService.selectBoard(commandMap.getMap()); mv.addObject("map", map.get("map")); mv.addObject("list",map.get("list")); return mv; }
- 먼저 Controller에서 게시글 상세보기에 대한 코드를 위와 같이 수정한다. map을 다시 map과 list를 구분한 것은 map은 게시글의 정보가 담긴 것이고 list는 해당 게시물의 파일들의 정보를 담아놓은 것이다. 미리 Service단에서 구분하여 map에 키를 추가 해놓은 것이다.
1.2 Service
SampleServiceImpl.java
@Override public Map<String, Object> selectBoard(Map<String, Object> map) throws Exception { sampleDAO.updateHitCnt(map); Map<String, Object> resultMap = new HashMap<String,Object>(); resultMap.put("map", sampleDAO.selectBoard(map)); resultMap.put("list", sampleDAO.selectFileList(map)); return resultMap; }
- 새로운 Map을 만들어서 각각의 게시물의 정보와 파일들의 정보를 키를 "map"과 "list"로 추가하고 반환한다. 여기서 DAO단의 selectFileList(map) 메서드가 있는데 이 부분에서 파일들의 정보를 담은 리스트를 가져오는 것이다.
1.3 DAO
SampleDAO.java
@SuppressWarnings("unchecked") public List<Map<String,Object>> selectFileList(Map<String, Object> map) { return (List<Map<String,Object>>)selectList("sample.selectFileList",map); }
- 게시물 리스트를 불러오는 것과 비슷한 형식으로 사용된 것을 보여준다. 파일 리스트의 정보를 가져오는 것이기 때문에 전혀 다를 것이 없기 때문이다. 해당 map에는 게시물의 번호가 들어가 있다.
1.4 SQL
sample_SQL.xml
<select id="sample.selectFileList" parameterType="hashmap" resultType="hashmap"> <![CDATA[ SELECT IDX, ORIGINAL_FILE_NAME, ROUND(FILE_SIZE/1024) AS FILE_SIZE FROM TB_FILE1 WHERE BOARD_IDX=#{IDX} AND DEL_GB='N' ]]> </select>
- 받아온 게시물 번호를 넣어 게시물에 해당하는 파일들을 가져오는 SQL이다. 6번줄은 파일의 크기를 MB로 표현하기 위하기 위함이다.
1.5 JSP
boardDetail.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title>상세보기</title> <%@ include file="/WEB-INF/include/include-header.jspf" %> </head> <body> <table class="board_view"> <caption>상세보기</caption> <colgroup> <col width="15%"> <col width="35%"> <col width="15%"> <col width="*"> </colgroup> <tbody> <tr> <th>제목</th> <td>${map.TITLE}</td> <th>조회수</th> <td>${map.HIT_CNT }</td> </tr> <tr> <th>작성자</th> <td>${map.CREA_ID }</td> <th>작성시간</th> <td>${map.CREA_DTM }</td> </tr> <tr> <th>내용</th> <td colspan="3"> ${map.CONTENTS } </td> </tr> <tr> <th>첨부파일</th> <td colspan="3"> <c:forEach items="${list}" var="row" > <p> <input type="hidden" value="${row.IDX }" id="IDX"> <a href="#this" name="file">${row.ORIGINAL_FILE_NAME }</a> (${row.FILE_SIZE }Byte) </p> </c:forEach> </tr> </tbody> </table> <a href="#this" id="list" class="btn">목록으로</a> <a href="#this" id="modify" class="btn">수정하기</a> <%@ include file="/WEB-INF/include/include-body.jspf" %> <script type="text/javascript"> $(document).ready(function(){ $("#list").on("click",function(e){ e.preventDefault(); fn_openBoardList(); }) $("#modify").on("click",function(e){ e.preventDefault(); fn_openBoardModify(); }) $("a[name='file']").on("click",function(e){ e.preventDefault(); fn_fileDownload($(this)); }) }) function fn_openBoardList(){ var comSubmit = new ComSubmit(); comSubmit.setUrl("<c:url value='/sample/openBoardList.do'/>"); comSubmit.submit(); } function fn_openBoardModify(){ var idx = "${map.IDX}"; var comSubmit = new ComSubmit(); comSubmit.setUrl("<c:url value='/sample/openBoardModify.do'/>"); comSubmit.addParam("IDX",idx); comSubmit.submit(); } function fn_fileDownload(obj){ var comSubmit = new ComSubmit(); comSubmit.setUrl("<c:url value='/common/downloadFile.do'/>"); comSubmit.addParam("IDX",obj.parent().find("#IDX").val()); comSubmit.submit(); $("#commonForm").children().remove(); } </script> </body> </html>
- 38~48줄
Controller에서 넘겨준 list에 담긴 파일 정보와 함께 파일 다운로드 메뉴와 함께 작성하였다. 해당 idx는 게시물의 idx가 아니라 파일 idx이고, 파일 크기와 원본 이름을 표시하게 하였다. 이 방식은 게시물 리스트에서 상세보기로 들어갈 때의 처리와 비슷하다. 게시물 리스트에선 클릭한 게시물의 IDX를 jQuery로 넘겨주기 위하여 사용되었지만 여기에선 클릭한 파일의 IDX를 jQuery로 넘겨주기 위하여 사용되었다.
- 65~68, 83~89줄
해당 부분도 게시물 상세보기와 거의 동일하다. $("#commonForm").children().remove() 이 부분은 파일 다운로드 1회 뒤에 다시 다운로드를 위하여 파일 클릭 시에 addParam을 통해서 들어가는 파라미터가 배열로 들어가기 때문에 사용했던 파라미터들의 처리를 지워주기 위하여 사용했다. IDX는 게시물 번호를 말하는 것이다.
1.6 중간 테스트
현재까지 한 것을 테스트하면 이렇다.
게시글 상세보기 화면과 처리에 대한 로그이다. 물론 다운로드의 클릭에 대한 것은 아직 작성하지 않아 오류가 발생한다. 로그도 정상적으로 잘 처리됨을 볼 수 있다. 이제 다운로드의 실질적인 것을 작성해보자.
2. 다운로드
다운로드의 방식은
1. 클라이언트의 파일 다운로드 요청 -> 2. 서버에서 해당 요청을 받음 -> 3. 서버에서 요청에 해당되는 파일의 정보를 DB에 요청 -> 4. DB에서 해당 파일의 정보를 검색 -> 5. 해당 파일 정보를 서버로 넘겨줌 -> 6. 서버에서 해당 정보를 통해 파일 저장 경로의 파일을 가져옴 -> 7. 서버에서 가져온 파일 데이터를 클라이언트에게 보내줌
이런 과정을 거침으로써 다운로드가 완료되는 것이다. 이 과정을 처리하는 코드를 작성할 것이다.
2.1 Controller
CommonController.java
@Controller public class CommonController { @Resource(name="commonService") private CommonService commonService; @RequestMapping(value="/common/downloadFile.do") public void downloadFile(CommandMap commandMap, HttpServletResponse response) throws Exception{ Map<String,Object> map = commonService.selectFileInfo(commandMap.getMap()); String original_File_Name = (String)map.get("ORIGINAL_FILE_NAME"); String stored_File_Name = (String)map.get("STORED_FILE_NAME"); byte[] fileByte = FileUtils.readFileToByteArray(new File("C:\\dev1\\file\\"+stored_File_Name)); response.setContentType("application/octet-stream"); response.setContentLength(fileByte.length); response.setHeader("Content-Disposition", "attachment; fileName=\"" + URLEncoder.encode(original_File_Name,"UTF-8")+"\";"); response.setHeader("Content-Transfer-Encoding", "binary"); response.getOutputStream().write(fileByte); response.getOutputStream().flush(); response.getOutputStream().close(); } }
- 15줄
웹서버는 브라우저로 전송될 페이지가 html 인경우 text/html을 표준 MIME 타입으로 지정합니다. 그러나 필요에 의해서 이 MIME 타입을 변경하고자 할 경우나 또는 캐릭터의 인코딩셋을 변경하고자 할때 setContentType 메소드를 사용할 수 있습니다. octet-stream 이라는 놈은 이름 그대로 8비트 바이너리 배열을 의미하며 http나 이메일상에서 application 형식이 지정되지 않았거나 형식을 모를때 사용합니다. 결국 브라우저는 octet-stream 으로 MIME 타입이 지정된 경우 단지 바이너리 데이터로서 다운로드만 가능하게 처리하게 됩니다.
- 17줄
Content-Disposition 속성을 이용하여 데이터의 형식을 지정할 수 있는데 attachment로 지정되어 있는데 첨부 파일을 말한다. 그 뒤에 fileName이라고 적혀있는 것은 다운로드 할 때 기본으로 적혀있던 파일 이름을 말한다. 그 뒤에 인코더 되있는 부분은 한글파일의 경우 UTF-8로 인코딩 되어 있지 않으면 깨진 이름으로 나오는데 이를 방지하기 위함이다. attachment;와 fileName 사이에는 띄워쓰기를 꼭 해주어야 하고 인코드하는 뒤 부분 "\" 부분도 꼭 써줘야 다운로드가 가능하다.
- 18줄
Content-Transfer-Encoding 는 전송되는 데이터의 안의 내용물들의 인코딩 방식을 말하며 여기에선 binary 방식을 택한 것이다.
- 19줄
위에서 byte[] 타입으로 변환한 파일을 response를 통해 클라이언트로 보내준다.
- 21~22줄
response를 중지하고 닫아준다.
2.2 Service
CommonService.java
public interface CommonService { public Map<String, Object> selectFileInfo(Map<String, Object> map) throws Exception; }
CommonServiceImpl.java
@Service("commonService") public class CommonServiceImpl implements CommonService { @Resource(name="commonDAO") private CommonDAO commonDAO; @Override public Map<String, Object> selectFileInfo(Map<String, Object> map) throws Exception { return commonDAO.selectFileInfo(map); } }
- SampleService 와 동일한 형태를 하고 있다. 파일에 대한 정보를 가져오는 메서드가 선언되어있다.
2.3 DAO
CommonDAO.java
@Repository("commonDAO") public class CommonDAO extends AbstractDAO { @SuppressWarnings("unchecked") public Map<String, Object> selectFileInfo(Map<String, Object> map) { return (Map<String,Object>)selectOne("common.selectFileInfo",map); } }
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="common"> <select id="selectFileInfo" parameterType="hashmap" resultType="hashmap"> <![CDATA[ SELECT STORED_FILE_NAME, ORIGINAL_FILE_NAME FROM TB_FILE1 WHERE IDX = #{IDX} ]]> </select> </mapper>
- namespace를 common으로 지정하였고, selectFileInfo에서 IDX를 사용할 수 있는데 이는 상세보기 화면 구성 jsp에서 addParam을 통해 파라미터를 추가해주었을 때의 IDX 값이다.
2.5 테스트
여기까지 작성을 완료하였다면 이제 테스트를 해보자.
- 작성자는 크롬이라 따로 다운로드 화면은 뜨지 않지만 익스플로러의 경우는 익숙한 화면이 뜰 것이다.
- 로그를 보면 정상적으로 SQL이 작동하고 해당 파일의 정보가 로그에 적혀있는 것을 볼 수 있다.
'코딩 > Spring' 카테고리의 다른 글
Spring 개발 - 게시판 만들기(16) - AOP 적용하기 (0) | 2018.03.02 |
---|---|
Spring 개발 - 게시판 만들기(15) - 파일 다중 업로드 및 게시판 수정하기(파일 업로드) (0) | 2018.01.02 |
Spring 개발 - 게시판 만들기(13) - 파일 업로드 (1) | 2017.12.30 |
Spring 개발 - 게시판 만들기(12) - 게시글 수정하기, 삭제하기 (1) | 2017.12.26 |
Spring 개발 - 게시판 만들기(11) - 게시글 상세보기 (0) | 2017.12.26 |