32일차(3)/jsp(15) : 자료실, 파일 업로드/다운로드 기능 구현
**DB에 새 테이블 생성
- 게시판에 업로드된 파일의 정보를 저장하는 테이블
- orgFileName : 원본 파일명
- saveFileName : 서버에 저장된 파일명
(같은 이름의 파일이 있는 경우, 업로드되면서 이름이 바뀐다.)
- fileSize : 해당 파일의 크기. 몇바이트인지
- regdate : 파일 업로드 일자
<index> 수정 : 자료실 링크 추가
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%
//session scope에 id라는 키값으로 저장된 값이 있는지 읽어와 본다.(없으면 null)
String id=(String)session.getAttribute("id");
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/index.jsp</title>
</head>
<body>
<div class="container">
<%if(id != null) {%>
<p>
<a href="${pageContext.request.contextPath }/users/private/info.jsp"><%=id %></a>님 로그인중....
<a href="${pageContext.request.contextPath }/users/logout.jsp">로그아웃</a>
</p>
<%}else{ %>
<a href="${pageContext.request.contextPath }/users/loginform.jsp">로그인</a>
<%} %>
<h1>인덱스 페이지입니다.</h1>
<ul>
<li><a href="${pageContext.request.contextPath }/users/signup_form.jsp">회원가입</a></li>
<li><a href="${pageContext.request.contextPath }/private/study.jsp">회원전용공간(공부)</a></li>
<li><a href="${pageContext.request.contextPath }/private/game.jsp">회원전용공간(게임)</a></li>
<li><a href="file/list.jsp">자료실</a></li>
</ul>
</div>
</body>
</html>
/file/ <list.jsp>
<%@page import="test.file.dao.FileDao"%>
<%@page import="java.util.List"%>
<%@page import="test.file.dto.FileDto"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%
//파일 목록을 얻어와서
List<FileDto> list=FileDao.getInstance().getList();
//응답하기
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/file/private/list.jsp</title>
</head>
<body>
<div class="container">
<a href="${pageContext.request.contextPath }/file/private/upload_form.jsp">업로드 하기</a>
<h3>자료실 목록 보기</h3>
<table>
<thead>
<tr>
<th>번호</th>
<th>작성자</th>
<th>제목</th>
<th>파일명</th>
<th>크기</th>
<th>등록일</th>
</tr>
</thead>
<tbody>
<%for(FileDto tmp:list) {%>
<tr>
<td><%=tmp.getNum() %></td>
<td><%=tmp.getWriter() %></td>
<td><%=tmp.getTitle() %></td>
<td>
<a href="download.jsp?num=<%=tmp.getNum() %>"><%=tmp.getOrgFileName() %></a>
</td>
<td><%=tmp.getFileSize() %></td>
<td><%=tmp.getRegdate() %></td>
</tr>
<%} %>
</tbody>
</table>
</div>
</body>
</html>
- 게시판을 테이블 형태로 작성하기!
- 파일에는 다운로드 링크를 함께 걸어준다.
- test.file.dto / test.file.dao 안에 DTO, DAO 생성
<FileDto>
package test.file.dto;
public class FileDto {
private int num;
private String writer;
private String title;
private String orgFileName;
private String saveFileName;
private long fileSize;
private String regdate;
public FileDto() {}
public FileDto(int num, String writer, String title, String orgFileName, String saveFileName, long fileSize,
String regdate) {
super();
this.num = num;
this.writer = writer;
this.title = title;
this.orgFileName = orgFileName;
this.saveFileName = saveFileName;
this.fileSize = fileSize;
this.regdate = regdate;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public String getWriter() {
return writer;
}
public void setWriter(String writer) {
this.writer = writer;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getOrgFileName() {
return orgFileName;
}
public void setOrgFileName(String orgFileName) {
this.orgFileName = orgFileName;
}
public String getSaveFileName() {
return saveFileName;
}
public void setSaveFileName(String saveFileName) {
this.saveFileName = saveFileName;
}
public long getFileSize() {
return fileSize;
}
public void setFileSize(long fileSize) {
this.fileSize = fileSize;
}
public String getRegdate() {
return regdate;
}
public void setRegdate(String regdate) {
this.regdate = regdate;
}
}
- 필드명은 가급적 오라클의 테이블명과 동일하게
<FileDao>
package test.file.dao;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import test.file.dto.FileDto;
import test.util.DbcpBean;
public class FileDao {
//static 필드
private static FileDao dao;
//외부에서 객체 생성하지 못하도록 생성자를 private로
private FileDao() {}
//자신의 참조값을 리턴해주는 메소드
public static FileDao getInstance() {
if(dao==null) {
dao=new FileDao();
}
return dao;
}
//파일 하나의 정보를 리턴하는 메소드
public FileDto getData(int num) {
FileDto dto=null;
//필요한 객체를 담을 지역변수를 미리 만들어둔다.
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
//Connection Pool에서 Connection 객체를 하나 얻어온다.
conn = new DbcpBean().getConn();
//실행할 sql문의 뼈대 구성하기
String sql = "SELECT writer, title, orgFileName, saveFileName, fileSize, regdate"
+ " From board_file"
+ " WHERE num=?";
//sql문의 ?에 바인딩할게 있으면 한다.
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, num);
//SELECT문을 수행하고 결과값을 받아온다.
rs = pstmt.executeQuery();
//반복문 돌면서 resultSet에서 필요한 값을 얻어낸다.
if (rs.next()) {
dto=new FileDto();
dto.setNum(num);
dto.setWriter(rs.getString("writer"));
dto.setTitle(rs.getString("title"));
dto.setOrgFileName(rs.getString("orgFileName"));
dto.setSaveFileName(rs.getString("saveFileName"));
dto.setFileSize(rs.getLong("fileSize"));
dto.setRegdate(rs.getString("regdate"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (rs != null)
rs.close();
if (pstmt != null)
pstmt.close();
if (conn != null)
conn.close(); //Connection Pool에 Connection 반납하기
} catch (Exception e2) {
}
}
return dto;
}
//업로드된 파일 정보를 DB 에 저장하는 메소드
public boolean insert(FileDto dto) {
Connection conn = null;
PreparedStatement pstmt = null;
int rowCount = 0;
try {
conn = new DbcpBean().getConn();
String sql = "INSERT INTO board_file"
+ " (num, writer, title, orgFileName, saveFileName, fileSize, regdate)"
+ " VALUES(board_file_seq.NEXTVAL, ?, ?, ?, ?, ?, SYSDATE)";
pstmt = conn.prepareStatement(sql);
//?에 바인딩
pstmt.setString(1, dto.getWriter());
pstmt.setString(2, dto.getTitle());
pstmt.setString(3, dto.getOrgFileName());
pstmt.setString(4, dto.getSaveFileName());
pstmt.setLong(5, dto.getFileSize());
//INSERT or UPDATE or DELETE문을 수행하고 수정,삭제,추가된 row의 개수 리턴
rowCount = pstmt.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (pstmt != null)
pstmt.close();
if (conn != null)
conn.close();
} catch (Exception e2) {
}
}
if (rowCount > 0) {
return true;
} else {
return false;
}
}
//파일 목록을 리턴해주는 메소드
public List<FileDto> getList() {
//파일 목록을 담을 ArrayList 객체 생성
List<FileDto> list=new ArrayList<FileDto>();
//필요한 객체를 담을 지역변수를 미리 만들어둔다.
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
//Connection Pool에서 Connection 객체를 하나 얻어온다.
conn = new DbcpBean().getConn();
//실행할 sql문의 뼈대 구성하기
String sql = "SELECT num, writer, title, orgFileName, fileSize, TO_CHAR(regdate, 'YYYY.MM.DD HH24:MI') regdate"
+ " FROM board_file"
+ " ORDER BY num DESC";
//sql문의 ?에 바인딩할게 있으면 한다.
pstmt = conn.prepareStatement(sql);
//SELECT문을 수행하고 결과값을 받아온다.
rs = pstmt.executeQuery();
//반복문 돌면서 resultSet에서 필요한 값을 얻어낸다.
while (rs.next()) {
//FileDto 객체에 select된 row하나의 정보를 담고
FileDto dto=new FileDto();
dto.setNum(rs.getInt("num"));
dto.setWriter(rs.getString("writer"));
dto.setTitle(rs.getString("title"));
dto.setOrgFileName(rs.getString("orgFileName"));
dto.setFileSize(rs.getLong("fileSize"));
dto.setRegdate(rs.getString("regdate"));
//ArrayList 객체에 누적시킨다.
list.add(dto);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (rs != null)
rs.close();
if (pstmt != null)
pstmt.close();
if (conn != null)
conn.close(); //Connection Pool에 Connection 반납하기
} catch (Exception e2) {
}
}
return list;
}
}
- 테이블 전체를 보여줄 getList 메소드 만들기 (SELECT * )
- 반복문을 돌면서 rs(resultset)에 담겨있는 내용을 하나씩 list에 담기!
- ResultSet에서 빼내서 dto의 setter메소드를 사용해서 담는다.
- FileSize 는 long 타입이기 때문에 .getLong 을 써준다.
/file/private/ <upload_form.jsp>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/file/private/upload_form.jsp</title>
</head>
<body>
<div class="container">
<h3>파일 업로드 폼입니다.</h3>
<!--
파일 업로드 폼 작성방법
1. method="post"
2. enctype="multipart/form-data"
3.<input type="file" />
- enctype-"multipart/form-data"가 설정된 폼을 전송하면
폼전송된 내용을 추출할때 HttpServletRequest 객체로 추출할 수 없다.
MultipartRequest 객체를 이용해서 추출해야 한다.
-->
<form action="upload.jsp" method="post" enctype="multipart/form-data">
<div>
<label for="title">제목</label>
<input type="text" name="title" id="title" />
</div>
<div>
<label for="myFile">첨부파일</label>
<input type="file" name="myFile" id="myFile" />
</div>
<button type="submit">업로드</button>
</form>
</div>
</body>
</html>
<form action="upload.jsp" method="post" enctype="multipart/form-data">
- 특정 파일을 폼 전송으로 보낼 수 있다.
- input type="file" 을 사용해서 파일을 업로드할 수 있는 버튼을 만든다.
@WebFilter(urlPatterns = {"/private/*", "/users/private/*", " /file/private/* "})
- 회원만 업로드할 수 있는 게시판으로 만들고 싶다면, 로그인 필터에 위 내용 추가하기
- private 하위에 있는 모든 웹페이지에 접근할 때 회원 로그인이 필요하다.
/file/private/ <upload.jsp>
<%@page import="test.file.dao.FileDao"%>
<%@page import="test.file.dto.FileDto"%>
<%@page import="com.oreilly.servlet.multipart.DefaultFileRenamePolicy"%>
<%@page import="com.oreilly.servlet.MultipartRequest"%>
<%@page import="java.io.File"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%
//파일 시스템 상에서 webapp의 upload 폴더까지의 절대경로를 얻어낸다.
String realPath=application.getRealPath("/upload");
//해당 경로를 access할 수 있는 파일 객체 생성
File f=new File(realPath);
if(!f.exists()){ //만일 폴더가 존재하지 않으면
f.mkdir();//upload 폴더 만들기
}
//cos.jar에 있는 MultipartRequest 클래스로 객체 생성하기
MultipartRequest mr=new MultipartRequest(request,//내부적으로 필요한 HttpServletRequest 객체 전달
realPath, //업로드된 파일을 저장할 경로
1024*1024*100, //최대 업로드 사이즈 제한
"utf-8", //한글 파일명 깨지지 않도록
new DefaultFileRenamePolicy()); //동일한 파일이 존재하면 자동으로 파일dmf rename해서 저장하도록
/*
위의 MultipartRequest 객체가 예외가 발생하지 않고 잘 생성되었다면 파일 업로드가 성공한 것이다.
따라서 mr에 들어있는 참조값을 이용해서 폼 전송된 값을 추출해서 저장만 잘 하면 된다.
*/
//2. 폼 전송되는 title을 읽어온다.
String title=mr.getParameter("title");
//3. 파일의 작성자(업로더)는 HttpSession 객체에서 읽어온다.
String writer=(String)session.getAttribute("id");
//4.추가로 원본 파일명, 저장된 파일명, 파일 사이즈도 얻어내서 FileDto 객체에 담아서
String orgFileName=mr.getOriginalFileName("myFile");
String saveFileName=mr.getFilesystemName("myFile");
long fileSize=mr.getFile("myFile").length();
//업로드된 파일의 정보를 FileDto에 담고
FileDto dto=new FileDto();
dto.setWriter(writer);
dto.setTitle(title);
dto.setOrgFileName(orgFileName);
dto.setSaveFileName(saveFileName);
dto.setFileSize(fileSize);
//DB에 저장하고 응답하기
boolean isSuccess=FileDao.getInstance().insert(dto);
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/file/private/upload.jsp</title>
</head>
<body>
<%if(isSuccess){ %>
<p>
<%=writer %>님이 업로드한 <%=orgFileName %>파일을 저장했습니다.
<a href="${pageContext.request.contextPath }/file/list.jsp">목록보기</a>
</p>
<p><%=realPath %></p>
<%}else{ %>
<p>
업로드 실패!
<a href="upload_form.jsp">목록보기</a>
</p>
<%} %>
</body>
</html>
<% String title=request.getParameter("title"); %>
- 위와 같이 작성할 경우, 파일데이터를 읽어오지 못한다.
- 파일 업로드는 일반 jsp 페이지를 읽어오는 것과는 다르다.
- request.getParameter를 쓰는 일반적인 방식으로는 추출할 수 없고, 어떤 라이브러리가 필요하다.
** 서블릿츠 닷컴 : https://servlets.com/
- 링크에서 cos 압축파일 다운로드: http://servlets.com/cos/
- cos 라이브러리에 들어있는 클래스들! 위의 기능을 사용해보려고 한다.
- 압축 해제해서 lib 폴더에 들어가면 cos.jar 파일이 있다.
- 파일 업로드에 필요한 여러 클래스를 제공해주는 라이브러리이다.
- WEB-INF의 lib 폴더 안에 넣어주기!
- webapp에 클라이언트가 접근할 수 있는 upload 폴더 생성
MultipartRequest mr=new MultipartRequest(request, realPath, 1024*1024,
"utf-8", new DefaultFileRenamePolicy());
- MultipartRequest 객체 생성. 5개의 인자를 가진다.
(요청, 실제 경로, 최대 크기, 인코딩, 이름을 변경하는 메소드 객체)
- 기본객체인 application 객체를 사용해서 업로드 폴더까지의 절대경로를 얻어낸다.
- cos.jar에 들어있는 객체들
- MultipartRequest 객체가 예외가 발생하지 않고 잘 생성되었다면 파일 업로드에 성공한 것이다.
- 따라서 mr에 들어있는 참조값을 이용해서 폼에 전송된 값을 추출해서 저장만 잘 하면 된다.
mr.getParameter("title"); : 업로드한 파일의 제목 값을 얻어낼 수 있다.
- MultipartRequest 객체에 어떤 메소드가 있는지 살펴보기.
- 파일을 저장한 경로를 담은 realPath가 함께 나오도록 했다.
- 똑같은 파일을 업로드하면 뒤에 자동으로 숫자가 붙는다.
- 파일명은 업로드 시 달라질 수 있기 때문에, 변경전/변경후 파일명이 모두 관리되어야 할 필요가 있다.
(현재 업로드한 파일의 이름이 같은 경우 자동으로 변경하게 하는 메소드를 사용함)
- orgFileName : 원본 파일명은 파일 목록을 보여주는 데에 필요
saveFileName : 저장된 파일명은 파일을 다운로드하는 데에 필요
- 해당 경로(realPath)를 복사해서 탐색기에 넣어보면 파일이 들어가있는 것을 볼 수 있다.
- 업로드한 파일을 다운로드 해보기
/file/download.jsp 생성
<%@page import="java.io.BufferedOutputStream"%>
<%@page import="java.net.URLEncoder"%>
<%@page import="java.io.File"%>
<%@page import="java.io.FileInputStream"%>
<%@page import="test.file.dto.FileDto"%>
<%@page import="test.file.dao.FileDao"%>
<%@ page language="java" contentType="application/octet-stream; charset=UTF-8"
pageEncoding="UTF-8"%>
<%
// 1. get 방식 파라미터로 전달되는 다운로드시켜줄 파일의 번호를 읽어온다.
int num=Integer.parseInt(request.getParameter("num"));
/*
2. DB에서 다운로드시켜줄 파일의 정보를 읽어온다.
어떤 파일명으로 upload 폴더에 저장되어 있는지 => saveFileName
업로드 당시 원본 파일명은 무엇인지 => orgFileName
파일의 사이즈는 어떻게 되는지 => fileSize
- 다운로드 시켜주기 위해서는 위의 세가지 정보가 필요하다.
*/
FileDto dto=FileDao.getInstance().getData(num);
//3. 서버의 파일시스템(upload) 에 저장된 파일에서 바이트 알갱이를 읽어서 출력한다(다운로드)
String orgFileName=dto.getOrgFileName();
String saveFileName=dto.getSaveFileName();
//다운로드 시켜줄 파일의 실제 경로 구성하기
String path=application.getRealPath("/upload") + File.separator+saveFileName;
//다운로드할 파일에서 읽어들일 스트림 객체 생성하기
FileInputStream fis=new FileInputStream(path);
//다운로드 시켜주는 작업을 한다. (실제 파일 데이터와 원본파일명을 보내줘야한다.)
//다운로드 시켜주는 작업을 한다.
String encodedName=null;
//한글 파일명 세부처리
if(request.getHeader("User-Agent").contains("Firefox")){
//벤더사가 파이어 폭스인경우
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(dto.getFileSize());
//클라이언트에게 출력할수 있는 스트림 객체 얻어오기
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();
%>
- 파일명에 다운로드 링크 추가하기.
- <a>로 감싸주고, get방식으로 전송되는 파일 번호도 같이 넣어준다.
- num을 이용해서 저 3가지 값(FileDto)를 가져오는 메소드를 만들어야 한다.(FileDao에)
<FileDao>에 메소드 추가 (getData)
//파일 하나의 정보를 리턴하는 메소드
public FileDto getData(int num) {
FileDto dto=null;
//필요한 객체를 담을 지역변수를 미리 만들어둔다.
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
//Connection Pool에서 Connection 객체를 하나 얻어온다.
conn = new DbcpBean().getConn();
//실행할 sql문의 뼈대 구성하기
String sql = "SELECT writer, title, orgFileName, saveFileName, fileSize, regdate"
+ " From board_file"
+ " WHERE num=?";
//sql문의 ?에 바인딩할게 있으면 한다.
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, num);
//SELECT문을 수행하고 결과값을 받아온다.
rs = pstmt.executeQuery();
//반복문 돌면서 resultSet에서 필요한 값을 얻어낸다.
if (rs.next()) {
dto=new FileDto();
dto=new FileDto();
dto.setNum(num);
dto.setWriter(rs.getString("writer"));
dto.setTitle(rs.getString("title"));
dto.setOrgFileName(rs.getString("orgFileName"));
dto.setSaveFileName(rs.getString("saveFileName"));
dto.setFileSize(rs.getLong("fileSize"));
dto.setRegdate(rs.getString("regdate"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (rs != null)
rs.close();
if (pstmt != null)
pstmt.close();
if (conn != null)
conn.close(); //Connection Pool에 Connection 반납하기
} catch (Exception e2) {
}
}
return dto;
}
- application 메소드를 통해 realPath 라는 경로를 얻어낼 수 있다.
- 저 application은 Servlet Context type 객체이다. 이 경로는 서버가 어떻게 세팅되었느냐에 따라 다르다!
그래서 메소드를 통해서 얻어낼 수밖에 없다.
String path=application.getRealPath("/upload") + File.separator+saveFileName;
- 전체 경로를 구성한다.
- byte[] 배열을 돌면서 응답해낼 내용을 추출해낸다.
- 파일로부터 네트워크로 출력해낸 것으로,
클라이언트에게 출력할 수 있는 Stream으로 보내주는 것이다. 저 자체가 응답이다!
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
→ <%@ page language="java" contentType="application/octet-stream; charset=UTF-8" pageEncoding="UTF-8"%>
- 웹브라우저에게 이 jsp파일에서 '파일을 응답할 것'이라고 알려주어야 한다.
따라서 맨 위에 있는 text/html 을 바꾸어주기!
- 이것을 mime type이라고 부른다.(마임 타입)
- 다운로드를 위해서는 3가지가 필요하다.
1)저장된 파일명 2)파일의 크기 3)원본파일 이름
- 파일을 읽어들이기 위해 필요한 정보이다.
- 저장된 파일명(경로)을 이용해서 FileInputStream 객체를 만들 수 있다.
- 원본 파일명은 (인코딩되어) 경로가 만들어지는 과정으로 전달된다.
- 파일의 크기를 알려주고, 반복문을 돌면서 FileInputStream 을 통해서 출력해준다.
- 파일을 업로드하면 DB에 저장되고, 개별 파일을 누르면 다운로드도 가능하다!!
'국비교육(22-23)' 카테고리의 다른 글
33일차(2)/jsp(17) : 자료실 파일 삭제 기능 구현, GIT 연습 (0) | 2022.11.24 |
---|---|
33일차(1)/jsp(16) : 자료실, 파일 업로드/다운로드 코드 리뷰 (0) | 2022.11.23 |
32일차(2)/jsp(14) : 회원정보 수정, 회원 탈퇴 기능 구현 (0) | 2022.11.22 |
32일차(1)/jsp(13) : request / session / filter 복습 (0) | 2022.11.22 |
31일차(2)/jsp(12) : 회원전용 페이지 기능 구현하기(Filter) (0) | 2022.11.21 |