본문 바로가기

반응형
검색 > 페이징 > Ajax > 파일 업로드 > 파일 다운로드

 

오늘은 파일 다운로드를 구현해 보겠습니다. 이번엔 이전과는 조금 다르게 사용자의 시점을 기준으로 구현해보도록 하겠습니다.

1. read.jsp 수정

<!-- 이전 코드 -->
    <c:forEach items='${imgList }' var='imgs' varStatus='status'>
        <c:if test='${status.count <= imgNum }'>
            <img src="${imgs.savePath}/${imgs.saveName}" />
            <!-- 다운로드를 위한 코드 추가 -->
            <a href="${path }/downImg?fileSeq=${imgs.fileSeq}">[다운]</a>
            <c:if test="${status.count != imgNum}">
                <br>
            </c:if>
        </c:if>
    </c:forEach>
<!-- 이전 코드 -->

결과물

2. Controller 작성

	@RequestMapping(value = "downImg")
	public ModelAndView imgDown(@RequestParam("fileSeq") int fileSeq, ModelAndView mav) {
		imgSer.downImg(fileSeq, mav);

		mav.setViewName("ImgDownView");

		return mav;
	}

 

3. Img Service, ServiceImpl 작성

// Img Service
	void downImg(int fileSeq, ModelAndView mav);
    
// Img ServiceImpl
	@Override
	public void downImg(int fileSeq, ModelAndView mav) {
		Map<String, Object> map = imgDao.downImg(fileSeq);
		
		mav.addObject("dto", map);
	}

 

4. Img Dao, DaoImpl 작성

// Img Dao
	Map<String, Object> downImg(int fileSeq);
    
// Img DaoImpl
	@Override
	public Map<String, Object> downImg(int fileSeq){
		return sqlSession.selectOne("ImgMapper.downImg", fileSeq);
	}

 

5. Img Mapper 작성

    <select id="downImg" resultMap="imgMap">
        SELECT
            FILE_SEQ,
            REAL_NAME,
            SAVE_NAME,
            REG_DATE,
            SAVE_PATH,
            BOARD_SEQ
        FROM BOARD_IMG
        WHERE FILE_SEQ = #{fileSeq}
    </select>

 

6. File Dowload 작성

package com.JoAri.CRUD.img;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.net.URLEncoder;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.view.AbstractView;

@Component("ImgDownView")
public class FileDownload extends AbstractView {

	@Override
	protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
			HttpServletResponse response) throws Exception {
		/*
		 * ModelAndView 객체에 addObject( ) 해서 담은 내용은 Map 객체에서 얻어낼수 있다.
		 */
		@SuppressWarnings("unchecked")
		Map<String, Object> imgMap = (Map<String, Object>) model.get("dto");
		// 파일을 다운로드 하는 작업을 해준다.
		String orgFileName = imgMap.get("realName").toString();
		String saveFileName = imgMap.get("saveName").toString();

		// 다운로드 시켜줄 파일의 실제 경로 구성하기
		// File.separator 는 window 에서는 \ , linux 에서는 / 를 얻어오게 된다.
		String path = request.getRealPath("/resources/images/") + File.separator + saveFileName;
		// 다운로드할 파일에서 읽어들일 스트림 객체 생성하기
		FileInputStream fis = new FileInputStream(path);
		// 다운로드 시켜주는 작업을 한다. (실제 파일 데이터와 원본파일명을 보내줘야한다.)
		// 다운로드 시켜주는 작업을 한다.
		String encodedName = null;
		// 한글 파일명 세부처리
		if (request.getHeader("User-Agent").contains("whale")) {
			// 벤더사가 파이어 폭스인경우
			encodedName = new String(orgFileName.getBytes("utf-8"), "ISO-8859-1");
		} else { // 그외 다른 벤더사
			encodedName = URLEncoder.encode(orgFileName, "utf-8");
			// 파일명에 공백이있는 경우 처리
			encodedName = encodedName.replaceAll("\\+", " ");
		}

		// 응답 헤더 정보 설정
		response.setHeader("Content-Disposition", "attachment;filename=" + encodedName);
		response.setHeader("Content-Transfer-Encoding", "binary");

		// 다운로드할 파일의 크기 읽어와서 다운로드할 파일의 크기 설정
//		response.setContentLengthLong(fileSize);

		// 클라이언트에게 출력할수 있는 스트림 객체 얻어오기
		// response.getOutputStream() 메소드는 클라이언트에게 출력할수 있는 OutputStream 객체를 반환한다.
		BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream());
		// 한번에 최대 1M byte 씩 읽어올수 있는 버퍼
		byte[] buffer = new byte[1024 * 1024];
		int readedByte = 0;
		// 반복문 돌면서 출력해주기
		while (true) {
			// byte[] 객체를 이용해서 파일에서 byte 알갱이 읽어오기
			readedByte = fis.read(buffer);
			if (readedByte == -1)
				break; // 더이상 읽을 데이터가 없다면 반복문 빠져 나오기
			// 읽은 만큼 출력하기
			bos.write(buffer, 0, readedByte);
			bos.flush(); // 출력
		}
		// FileInputStream 닫아주기
		fis.close();
	}

}

 

7. 결과물

 

후기

띠용~? 파일 다운로드는 복잡하지가 않네요? 경로 때문에 살짝 고생할 뻔했지만 파일 업로드를 겪고 나니 고생이라고 느껴지지도 않네요. 코딩 순서를 바꾼 영향일까요? 파일 업로드를 해본 경험 때문일까요? 앞으로도 이 순서대로 해봐야겠습니다.

오늘의 체크 포인트는 getRealPath() 메소드 입니다. 지금 블로그 내에는 나타나있지 않지만 제 이클립스에서는 경고 표시와 함께 The method getRealPath(String) from the type ServletRequest is deprecated 라는 문구가 뜹니다. "ServletRequest 유형에서 GetRealPath(String) 메서드가 사용되지 않습니다" 라는 뜻인데 그런데 왜 사용할 수 있게 되어 있을까요?
제가 따로 알아본 바로는 이제는 사용하지 않는 방법이라는 것입니다. 오류가 발생하기 쉽거나 보안 문제가 있거나 더 현대적인 대체 방법으로 대체되는 등 다양한 이유로 쓰지 말라고 권장하는 것 같네요. 또한, 실제 디스크 파일 시스템에 의존하고 모든 배포 시나리오(예: 클라우드 환경 또는 WAR 파일)에서 액세스할 수 없으니 getRealPath를 사용하는 것을 피하고 Spring과 같은 Servlet API 또는 프레임워크에서 제공하는 대체 접근 방식을 사용하는 것이 좋다고 하더라구요. 이 메소드 보다는 리소스 추상화를 사용하는 것이 좋다고 해서 찾아보니 다음과 같은 예시를 찾았습니다.
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;

@Autowired
private ResourceLoader resourceLoader;

String path = "/resources/images/" + saveFileName;
Resource resource = resourceLoader.getResource( "classpath:" + path);

이로써 CRUD 기초 프로젝트를 모두 마쳤습니다. 쉽게 쉽게 금방 구현한 것도 있었고, 실수를 하거나 원인을 몰라서 늦게 찾은 적도 많았던 것 같습니다. 하면서 든 생각은 기초가 참 중요하구나 그리고 실제 프로젝트를 하고 계신 개발자분들은 이보다 더 복잡한 기능들을 구현할 텐데 대단하고 고생이 많구나라고 느꼈습니다. 당분간은 이번에 느꼈던 점들을 제 것으로 소화 시켜야겠습니다.
CRUD 프로젝트는 이만 마치고 다음은 전자결재 프로젝트로 찾아뵙겠습니다! 그동안 제 CRUD 프로젝트를 응원해 주신 분들 감사합니다. 빠잇~!
반응형

'Spring > CRUD Project' 카테고리의 다른 글

[CRUD] 파일 업로드 및 조회 2 : 수정 및 출력  (1) 2023.12.05
[CRUD] 파일 업로드 및 조회  (1) 2023.12.04
[CRUD] Ajax : 비동기 통신  (1) 2023.11.30
[CRUD] 페이징  (0) 2023.11.29
[CRUD] 검색  (1) 2023.11.28
댓글