반응형
검색 > 페이징 > 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 |