파일 다운로드
현재 freeView에서 다음과 같이 파일이 2개가 있다. 이를 누렀을 때 다운로드가 되도록 해보자.
freeView.jsp의 첨부파일 태그를 보면 다음과 같이 되어있다.
<tr>
<th>첨부파일</th>
<td>
<c:forEach var="f" items="${freeBoard.attaches}" varStatus="st">
<div> 파일 ${st.count} <a href="<c:url value='/attach/download/${f.atchNo}' />" target="_blank">
<span class="glyphicon glyphicon-save" aria-hidden="true"></span> ${f.atchOriginalName}
</a> Size : ${f.atchFancySize} Down : ${f.atchDownHit}
</div>
</c:forEach>
</td>
</tr>
여기서 주목할건 <a> 태그밖에 없다. <a>태그의 target 속성의 "_blank"는 새창을 연다.
즉 위 <a>태그는 새창을 열면서 서버:포트/contextPath/attach/download/${f.atchNo} (localhost:8080/contextPath(없으면 생략)/attach/download/첨부파일번호)로 요청한다
우리는 이 요청에 맞는 컨트롤러를 만들고 첨부파일번호를 이용해 DB 조회후, 파일이름과 경로를 얻는다.
그리고 해당파일을 찾아 response에 해당파일을 전달하면 된다.
IAttachDao, attach.xml
먼저 첨부파일번호를 가지고 DB를 조회하는 IAttachDao, attach.xml에 메소드를 추가하자.
그리고 다운로드 할 때마다 다운로드 횟수가 증가하는 메소드도 추가하자.
IAttachDao.java
/** 첨부파일 상세 조회 */
public AttachVO getAttach(int atchNo);
/** 다운로드 횟수 증가 */
public int increaseDownHit(int atchNo);
attach.xml
<select id="getAttach" parameterType="int" resultType="com.study.attach.vo.AttachVO">
SELECT atch_no
, atch_file_name
, atch_original_name
, atch_file_size
, atch_content_type
, atch_path
FROM attach
WHERE atch_no = #{atchNo}
</select>
<update id="increaseDownHit" parameterType="int">
UPDATE attach
SET atch_down_hit = atch_down_hit+1
WHERE atch_no = #{atchNo}
</update>
IAttachService, AttachServiceImpl
IAttachService와 AttachServiceImpl도 만들어줍시다.
IAttachService.java
package com.study.attach.service;
import com.study.attach.vo.AttachVO;
import com.study.exception.BizException;
import com.study.exception.BizNotEffectedException;
import com.study.exception.BizNotFoundException;
public interface IAttachService {
/** 첨부파일 상세 조회 */
public AttachVO getAttach(int atchNo) throws BizNotFoundException;
/** 다운로드 횟수 증가 */
public void increaseDownHit(int atchNo) throws BizNotEffectedException;
}
AttachServiceImpl.java
package com.study.attach.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.study.attach.dao.IAttachDao;
import com.study.attach.vo.AttachVO;
import com.study.exception.BizException;
import com.study.exception.BizNotEffectedException;
import com.study.exception.BizNotFoundException;
@Service
public class AttachServiceImpl implements IAttachService {
@Autowired
private IAttachDao attachDao;
@Override
public AttachVO getAttach(int atchNo) throws BizNotFoundException {
AttachVO vo = attachDao.getAttach(atchNo);
if (vo == null) {
throw new BizNotFoundException("첨부파일 [" + atchNo + "]에 대한 조회 실패");
}
return vo;
}
@Override
public void increaseDownHit(int atchNo) throws BizNotEffectedException {
int cnt=attachDao.increaseDownHit(atchNo);
if(cnt==0) throw new BizNotEffectedException();
}
}
단순히 AttatchVO를 DB에서 가져오는건 어렵지 않다.
문제는 파일에 대한 정보를 DB에서 가져온 이후이다.
StudyattachUtils
StudyAttachUtils에 다음의 메소드를 추가하자.
public File getFileFromAttachVO(AttachVO attach) throws IOException, BizNotFoundException {
String originalName = new String(attach.getAtchOriginalName().getBytes("utf-8"), "iso-8859-1");//파일에 한글명이 있을경우
String fileName = attach.getAtchFileName(); //저장되어있는 파일이름. 랜덤값
String filePath = attach.getAtchPath(); // 저장되어있는 폴더 경로
String path = uploadPath + File.separatorChar + filePath;
File file = new File(path, fileName);
if (!file.isFile()) throw new BizNotFoundException("파일없음");
return file;
}
AttachController
파일번호 (Pirmary Key)를 가지고 DB에서 파일경로, 파일이름이 포함된 AttachVO를 가지고 왔으면
이를 이용해 경로에 있는 실제 파일을 찾아 response에 추가하면 된다.
그 후 파일다운로드를 위해 response에 헤더를 변경해주면 된다.
(헤더를 변경하지 않으면 DispatcherServlet은 일반적인 text/html을 응답하려고 할것이다.)
AttachController.java
package com.study.attach.web;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import com.study.attach.service.IAttachService;
import com.study.attach.vo.AttachVO;
import com.study.exception.BizNotEffectedException;
import com.study.exception.BizNotFoundException;
import com.study.free.web.FreeBoardController;
@Controller
public class AttachController {
@Value("#{util['file.upload.path']}")
private String uploadPath;
@Autowired
private IAttachService attachService;
// @PathVariable 사용하여 url상의 경로를 변수에 할당 "/attach/download/25625"
@RequestMapping("/attach/download/{atchNo:[0-9]{1,16}}")
public void process(@PathVariable(name = "atchNo") int atchNo, HttpServletResponse resp) throws Exception {
try {
// 서비스를 통해 첨부파일 가져오기
AttachVO vo = attachService.getAttach(atchNo);
File file = attachUtils.getFileFromAttachVO(vo); //attach로 파일찾기
//파일을 브라우저가 다운로드하기위한 header설정
resp.setHeader("Content-Type", "application/octet-stream;");
resp.setHeader("Content-Disposition", "attachment;filename=\"" + attach.getAtchOriginalName() + "\";");
resp.setHeader("Content-Transfer-Encoding", "binary;");
resp.setContentLength((int) f.length()); // 진행율
resp.setHeader("Pragma", "no-cache;");
resp.setHeader("Expires", "-1;");
// 저장된 파일을 응답객체의 스트림으로 내보내기, resp의 outputStream에 해당파일을 복사
FileUtils.copyFile(file, resp.getOutputStream());
resp.getOutputStream().close();
attachService.increaseDownHit(atchNo);
} catch (BizNotFoundException e) {
printMessage(resp, "해당 첨부파일이 존재하지 않습니다.");
} catch (BizNotEffectedException e) {
e.printStackTrace(); //거의 일어나지않기때문에...
}catch (IOException e) {
resp.reset();
resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); // 500
}
}
//정상적인 다운로드가 안될 경우 메시지 처리
private void printMessage(HttpServletResponse resp, String msg) throws Exception {
resp.setCharacterEncoding("utf-8");
resp.setHeader("Content-Type", "text/html; charset=UTF-8");
PrintWriter out = resp.getWriter();
//target이 지정되지 않은 경우 history.back() 으로 처리
out.println("<script type='text/javascript'>");
out.println(" alert('" + msg + "');");
out.println(" self.close();");
out.println("</script>");
out.println("<h4>첨부파일 문제 " + msg + "</h4>");
out.println("<button onclick='self.close()'>닫기</button>");
}
}
헤더를 잠깐 살펴보자.
- Content-type: application/octet-stream;
- 8비트 단위의 binary data이다. 파일이기때문에 byte단위로 전송된다. - Content-Disposition : attachment;filename=\"" + originalName(변수) + "\";
- 다운로드 시 무조건 파일 다운로드 대화상자가 뜨도록 하는 속성.
filename= 은 대화상자의 이름이 된다. - Content-Transfer-Encoding : binary;
-이 헤더는 디코더가 메시지의 body를 원래의 포맷으로 바꾸기 위해 사용하는 디코딩방식이다. - Pragma : no-cache; HTTP 1.0 이전 버전의 클라이언트를 위한 헤더,
캐시가 캐시 복사본을 릴리즈 하기전에 원격 서버로 요청을 날려 유효성 검사를 강제하도록 함 - Expires : -1 (만료일: -1이면 다운로드의 제한시간 없음. 요즘엔 크게 중요하지않음)
(Pragma와 Expires는 pass)
여기까지 했으면 freeView에서 파일을 클릭하면 다음과 같이 다운로드가 된다.
파일이미지 보여주기
업로드된 파일을 이용해 img의 경우 미리보여주기를 해보자.
(원래는 img만 업로드 하게 한다든가 , 썸네일 보여주기 등으로 하는거지만
파일다운로드만 했는데 img 미리보기에 대한 요청이 많아서 급하게 하느라....)
freeView.jsp의 첨부파일부분에 다음과 같이 <img>태그를 추가해주자
<td>
<c:forEach var="f" items="${freeBoard.attaches}" varStatus="st">
<div> 파일 ${st.count} <a href="<c:url value='/attach/download/${f.atchNo}' />" target="_blank">
<span class="glyphicon glyphicon-save" aria-hidden="true"></span> ${f.atchOriginalName}
</a> Size : ${f.atchFancySize} Down : ${f.atchDownHit}
<img alt="" src="<%=request.getContextPath()%>/attach/showImg.wow?fileName=${f.atchFileName}&filePath=${f.atchPath}" width="50px" height="50px">
</div>
</c:forEach>
</td>
그리고 AttachController에 <img src="" > 에서의 요청을 처리할 컨트롤러를 만든다.
AttachController.java
//img파일 썸네일
@GetMapping("/attach/showImg.wow")
@ResponseBody
public ResponseEntity<byte[]> getFile(String fileName,String filePath){
File file=new File(uploadPath+File.separatorChar +filePath,fileName);
ResponseEntity<byte[]> result=null;
try {
HttpHeaders headers=new HttpHeaders();
headers.add("Content-Type", Files.probeContentType(file.toPath()));
result=new ResponseEntity<>(FileCopyUtils.copyToByteArray(file),headers,HttpStatus.OK );
}catch (IOException e) {
e.printStackTrace();
}
return result;
}
getFile( )은 파일 경로가 포함된 fileName을 파라미터로 받고 byte[ ]를 전송합니다.
byte[ ]로 이미지 파일 데이터를 전송할 때 신경 쓰이는 것은
브라우저에 보내주는 MIME 타입이 파일의 종류에 따라 달라지는 점입니다.
이를 해결하기 위해 Files.probeContentType( )을 이용해
적절한 MIME 타입 데이터를 Http의 헤더 메시지에 포함할 수 있도록 처리합니다.
src="/study4/attach/showImg.wow?fileName=ed2be855-fd73-453e-afd4-6641b5812b8a&filePath=free"
의 img태그의 미리보기가 완성됩니다.