.jsp
파일 원본 이름 옆 다운로드 a태그를 만들었다. 이 태그에 속성을 몇 개 추가한다. 이름은 마음대로 해도 된다.
1. sfolder - 서버 저장 폴더명
2. sfile - 실제 저장 파일 이름
3. ofile - 원본 파일 이름
<ul>
<c:forEach var="file" items="${article.fileInfos}">
<li>${file.originFile} <a href="#" class="filedown" sfolder="${file.saveFolder}" sfile="${file.saveFile}" ofile="${file.originFile}">[다운로드]</a>
</c:forEach>
</ul>
a 태그가 클릭되면 실행되는 이벤트 함수를 만들었다. (js)
이 함수는 파일 다운로드 form에 값을 넣고 submit 시킨다.
$('.filedown').click(function() {
$(document).find('[name="sfolder"]').val($(this).attr('sfolder'));
$(document).find('[name="ofile"]').val($(this).attr('ofile'));
$(document).find('[name="sfile"]').val($(this).attr('sfile'));
$('#downform').attr('action', '주소').attr('method', 'get').submit();
});
파일 다운로드 form은 hidden input으로만 구성되어있다. 주소로 parameter을 붙여 전송해도 되지만, 그 방식은 인코딩이 필요하기 때문에 form 방식을 많이 사용한다.
<form id="downform">
<input type="hidden" name="sfolder">
<input type="hidden" name="ofile">
<input type="hidden" name="sfile">
</form>
servlet-context.xml
파일 다운로드를 위해 설정이 필요하다. 다운로드에는 실제 view가 필요하지 않다. 따라서 서블릿 뷰를 만든다. 이 서블릿 뷰는 다른 뷰처럼 뒤에 확장자가 붙을 필요도 없고 앞에 파일 주소가 붙을 필요도 없다.
fileViewResolver가 있으면 order 번호 순서에 맞춰서 빈으로 등록된 자바 파일을 처리한다. order는 Servlet의 Load-on-startup 값과 유사하다.
FileDownLoadView 이름의 클래스를 빈으로 등록했다.
<beans:bean id="fileDownLoadView" class="com.ssafy.guestbook.view.FileDownLoadView"/>
<beans:bean id="fileViewResolver" class="org.springframework.web.servlet.view.BeanNameViewResolver">
<beans:property name="order" value="0" />
</beans:bean>
FileDownLoadView
1. 기본 생성자에서 ContentType을 미리 설정한다. setContentType은 AbstractView에 있는 메서드다. (getContentType포함)
2. AbstractView의 renderMergedOutputModel을 override 한다.
2-1. 실제 주소를 가져온다. 파일 업로드를 /upload폴더 밑에 했기 때문에 그 주소를 가져왔다.
2-2. 전송받은 모델을 사용 가능하게 타입 변경한다. 여기서는 Map으로 전송했기 때문에 Map으로 변환했다.
2-3. File객체를 생성해서 실제주소+구분자+저장 폴더에 있는 파일을 가져온다. 실제 저장된 파일 이름으로 접근해야 한다.
2-4. 생성자에서 지정한 ContentType을 response에 적용한다. response의 길이도 file에 맞춘다.
2-5. 브라우저 종류에 따라 인코딩을 다르게 한다. IE의 경우 \\+를 공백(%20)으로 교체하는 작업이 필요하다.
2-6. response의 header를 설정한 뒤에 outputStream을 받아온다.
2-7. fileInputStream으로 2-3의 file을 열어서 사용자 컴퓨터에 저장한다.
2-8. outputStream을 비운다.
public class FileDownLoadView extends AbstractView {
public FileDownLoadView() {
setContentType("apllication/download; charset=UTF-8");//실제로 볼 수 없고 다운로드만 가능한 contenttype
}
@Override
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
ServletContext ctx = getServletContext();
String realPath = ctx.getRealPath("/upload");
Map<String, Object> fileInfo = (Map<String, Object>) model.get("downloadFile"); // 전송받은 모델(파일 정보)
String saveFolder = (String) fileInfo.get("sfolder"); // 파일 경로
String originalFile = (String) fileInfo.get("ofile"); // 원본 파일명(화면에 표시될 파일 이름)
String saveFile = (String) fileInfo.get("sfile"); // 암호화된 파일명(실제 저장된 파일 이름)
File file = new File(realPath + File.separator + saveFolder, saveFile);
response.setContentType(getContentType());//생성자에서 지정한 contenttype을 response에 적용함
response.setContentLength((int) file.length());
String header = request.getHeader("User-Agent");
boolean isIE = header.indexOf("MSIE") > -1 || header.indexOf("Trident") > -1;
String fileName = null;
// IE는 다르게 처리(공백등..)
if (isIE) {
fileName = URLEncoder.encode(originalFile, "UTF-8").replaceAll("\\+", "%20");
} else {
fileName = new String(originalFile.getBytes("UTF-8"), "ISO-8859-1");
}
response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\";");
response.setHeader("Content-Transfer-Encoding", "binary");
OutputStream out = response.getOutputStream();
FileInputStream fis = null;
try {
fis = new FileInputStream(file);
FileCopyUtils.copy(fis, out);//서버에 있는걸 컴퓨터로 저장
} catch (Exception e) {
e.printStackTrace();
} finally {
if(fis != null) {
try {
fis.close();
}catch (IOException e) {
e.printStackTrace();
}
}
}
out.flush();
}
}
Controller
1. 파라미터 이름과 사용할 이름이 같아서 @RequestParam을 쓰지 않았다.
2. 새 Map을 만들어서 받은 정보를 저장한다.
3. ModelAndView 객체를 반환한다. 이때, view name은 "fileDownLoadView", model name은 "downloadFile", model Ojbect는 2에서 만든 Map 객체다.
4. 이후에 servlet-context.xml에서 빈으로 설정한 FileDownLoadView로 가서 다운로드를 마무리짓는다.
@GetMapping(value = "/download")
public ModelAndView downloadFile(String sfolder, String ofile, String sfile, HttpSession session) {
Map<String, Object> fileInfo = new HashMap<String, Object>();
fileInfo.put("sfolder", sfolder);
fileInfo.put("ofile", ofile);
fileInfo.put("sfile", sfile);
return new ModelAndView("fileDownLoadView", "downloadFile", fileInfo);
}
'기록 > BACKEND' 카테고리의 다른 글
[WEB] MyBatis 설정 (0) | 2022.04.25 |
---|---|
[Spring] Interceptor (0) | 2022.04.24 |
[Spring] File Upload (0) | 2022.04.22 |
[Spring] 예외처리하기 - ControllerAdvice (0) | 2022.04.21 |
[Spring] DI (0) | 2022.04.20 |
댓글