게시판 업로드 및 다운로드 구현

Jang Seok Woo·2020년 9월 14일
1

웹개발

목록 보기
17/31

게시판 업로드 및 다운로드 구현

개발환경 : Oracle11g, JSP

구현한 게시판에 이미지 업로드 및 다운로드를 구현하도록 한다.

유저가 글쓰기를 할 때 파일 및 그림을 업로드 할 수 있도록 구현하고,

다른 유저는 해당 글을 눌렀을 때 유저가 올린 파일 및 그림을 다운로드 할 수 있도록 구현하는게 목표다.

1. 업로드

먼저 업로드를 구현해 보도록 하자.

유저가 글을 쓸 경우 파일 업로드 하는 부분을 구현한다.

Write.jsp 에서 파일 받는 부분

<form name="form" method="post" action="writeAction.jsp" enctype="multipart/form-data">
		<table class="table table-striped" style="text-align: center; border: 1px solid #dddddd">
				<thead>
					<tr>
						<th colspan="2" style="background-color: #eeeeee; text-align: center;">게시판 글쓰기 양식</th>
					</tr>
				</thead>
				<tbody>
					<tr>
						<td><input type="text" class="form-control" placeholder="글 제목" name ="bbsTitle" maxlength="50"></td>
					</tr>
					<tr>
						<td><textarea class="form-control" placeholder="글 내용" name ="bbsContent" maxlength="2048" style="height:350px;"></textarea></td>
					</tr>
				</tbody>
			</table>
			   file: <input type="file" name="file"><br>

file: <input type="file" name="file"><br>

우선 이렇게 하면 업로드 하는 버튼이 생긴다.

기존에 writeAction.jsp로 Action과 method = post하는 부분이기 때문에 writeAction.jsp 부분에 파일을 업로드 할 수 있도록 코드를 추가하도록 한다.

<%@ page import="file.FileDAO" %>
<%@ page import="java.io.File" %>
<%@ page import="com.oreilly.servlet.multipart.DefaultFileRenamePolicy" %>
<%@ page import="com.oreilly.servlet.MultipartRequest" %>

먼저 FileDAO와 나머지 라이브러리를 import한다. 각각 파일을 업로드하는데 사용되는 라이브러리 이다.

FileDAO는 다음과 같이 만들어 준다.

FileDAO.java

package file;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;

public class FileDAO {
	
	private Connection conn;
	
	public FileDAO() {
		try {
			String dbURL = "jdbc:oracle:thin:@localhost:1521:xe";
			String dbID = "jsw";
			String dbPassword = "jsw";
			Class.forName("oracle.jdbc.OracleDriver");
			conn = DriverManager.getConnection(dbURL,dbID,dbPassword);
		} catch(Exception e) {
			e.printStackTrace();
		}
		
	}
	
	
	
	public int upload(String fileName, String fileRealName, int bbsID) {
		String SQL = "INSERT INTO bbs_file VALUES (?,?,?)";
		try {
			
			PreparedStatement pstmt = conn.prepareStatement(SQL);
			pstmt.setString(1, fileName);
			pstmt.setString(2, fileRealName);
			pstmt.setInt(3, bbsID);
			return pstmt.executeUpdate();
		} catch (Exception e) {
			
		}
		return -1;
		
	}
	
	

}

Upload 함수는 preparedstatement 를 이용하여 구현하였으며, Oracle에 테이블을 새로 만들어 주었다.

CREATE TABLE BBS_FILE(
FILENAME VARCHAR(20),
FILEREALNAME VARCHAR(20),
BBSID NUMBER);

결과적으로 bbsid는 필요가 없었지만 우선 저렇게 만들었다.

FileDTO.java도 만들어 주도록 한다.

그냥 File 클래스의 생성자 및 getter, setter이다.

FileDTO.java

package file;

public class FileDTO {
	
	String fileName;
	String fileRealName;
	
	public String getFileName() {
		return fileName;
	}
	public void setFileName(String fileName) {
		this.fileName = fileName;
	}
	public String getFileRealName() {
		return fileRealName;
	}
	public void setFileRealName(String fileRealName) {
		this.fileRealName = fileRealName;
	}
	
	public FileDTO(String fileName, String fileRealName) {
		super();
		this.fileName = fileName;
		this.fileRealName = fileRealName;
	}

	
	
}

이제 writeAction에 마무리로 코딩해주면 되는데,

Body 부분을 보면,

<body>
	<%
	String userID = null;
	if(session.getAttribute("userID")!=null);{
	userID = (String) session.getAttribute("userID");
	}
	BbsDAO bbsDAO = new BbsDAO();
	Bbs bbs= new Bbs();
	bbs.setBbsID(bbsDAO.getNewNext());
	int bbsID = bbs.getBbsID();
	String directory = application.getRealPath("/upload/"+bbsID+"/");
	
	File targetDir = new File(directory);
	if(!targetDir.exists()){
		targetDir.mkdirs();
	}
	
	int maxSize = 1024 * 1024 * 500;
	String encoding = "UTF-8";
	
	MultipartRequest multipartRequest
	= new MultipartRequest(request, directory, maxSize, encoding,
					new DefaultFileRenamePolicy());
	
	String fileName = multipartRequest.getOriginalFileName("file");
	String fileRealName = multipartRequest.getFilesystemName("file");
	
	String bbsTitle = multipartRequest.getParameter("bbsTitle");
	String bbsContent = multipartRequest.getParameter("bbsContent");
	bbs.setBbsTitle(bbsTitle);
	bbs.setBbsContent(bbsContent);
	
	if(userID == null){
		PrintWriter script = response.getWriter();
		script.println("<script>");
		script.println("alert('로그인을 하세요')");
		script.println("location.href='login.jsp'");
		script.println("</script>");
	} else{
		
		System.out.println("write action : check bbs parameter" + bbs.getBbsTitle());
		
		if(bbs.getBbsTitle() == null || bbs.getBbsContent() == null){
			PrintWriter script = response.getWriter();
			script.println("<script>");
			script.println("alert('입력이 안 된 사항이 있습니다.')");
			script.println("history.back()");
			script.println("</script>");
		}else{
			
			System.out.println("getNewNext before bbsDAO.write : " + bbs.getBbsID());
			int result = bbsDAO.write(bbs.getBbsTitle(), userID, bbs.getBbsContent());
			
			
			
			new FileDAO().upload(fileName, fileRealName, bbs.getBbsID());
			out.write("filename : " + fileName + "<br>");
			out.write("realfilename : " + fileName + "<br>");
			
			if (result==-1){
				PrintWriter script = response.getWriter();
				script.println("<script>");
				script.println("alert('글쓰기에 실패했습니다.')");
				script.println("history.back()");
				script.println("</script>");	
			}
			else{
				PrintWriter script = response.getWriter();
				script.println("<script>");
				script.println("location.href = 'bbs.jsp'");
				script.println("</script>");
			}
		}
	}
	%>	
</body>

추가한 부분은 크게 두 부분이다.

첫 부분은 여기다.

기존의 writeAction은 post방식 parameter로 변수들을 가져왔으나, 이번엔

<form name="form" method="post" action="writeAction.jsp" enctype="multipart/form-data">

와 같이 enctype 멀티파트/ 폼 데이터로 받아왔기 때문에 변수처리를 새로 해야한다. 안그러면 null처리가 됨.

String bbsTitle = multipartRequest.getParameter("bbsTitle");
String bbsContent = multipartRequest.getParameter("bbsContent");

이런식으로 멀티파트리퀘스트를 이용해 변수를 가져온다.

String fileName = multipartRequest.getOriginalFileName("file");
String fileRealName = multipartRequest.getFilesystemName("file");

파일 이름도 이렇게 가져온다. Realname과 name의 차이는 동일한 파일을 올렸을 시 컴퓨터에서 구분해서 저장시키고 다운받을 시 다시 원래의 이름으로 다운받도록 하기 위함이다.

BbsDAO bbsDAO = new BbsDAO();
	Bbs bbs= new Bbs();
	bbs.setBbsID(bbsDAO.getNewNext());
	int bbsID = bbs.getBbsID();
	String directory = application.getRealPath("/upload/"+bbsID+"/");
	
	File targetDir = new File(directory);
	if(!targetDir.exists()){
		targetDir.mkdirs();
	}
	
	int maxSize = 1024 * 1024 * 500;
	String encoding = "UTF-8";
	
	MultipartRequest multipartRequest
	= new MultipartRequest(request, directory, maxSize, encoding,
					new DefaultFileRenamePolicy());
	
	String fileName = multipartRequest.getOriginalFileName("file");
	String fileRealName = multipartRequest.getFilesystemName("file");
	
	String bbsTitle = multipartRequest.getParameter("bbsTitle");
	String bbsContent = multipartRequest.getParameter("bbsContent");
	bbs.setBbsTitle(bbsTitle);
	bbs.setBbsContent(bbsContent);

그리고 두 번째 부분,

System.out.println("getNewNext before bbsDAO.write : " + bbs.getBbsID());
int result = bbsDAO.write(bbs.getBbsTitle(), userID, bbs.getBbsContent());
			
new FileDAO().upload(fileName, fileRealName, bbs.getBbsID());

로그인 여부, 글 작성칸 입력여부 등 필터를 거친 후, DB에 저장할 때 다음과 같이 upload함수를 이용해준다.

게시판에 파일을 업로드하면 항상 모든 파일을 불러와야 하는게 아니라 해당하는 파일만 불러와야하기에 다음과 같이 조치를 취해준다.

	String directory = application.getRealPath("/upload/"+bbsID+"/");
	
	File targetDir = new File(directory);
	if(!targetDir.exists()){
		targetDir.mkdirs();
	}

여기서 bbsID를 변수로 함께 넘겨주는 이유는 루트 파일 아래에 게시판 글 별로 업로드 파일을 따로 보관하여 글을 열었을 때 해당되는 파일만 띄우기 위함이다.

저 upload는 어디에 만들어 둔 폴더냐면, 해당 이클립스를 켜둔채로 프로젝트 폴더로 간다.

/root/eclipse-workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/BBS

다음과 같이 워크스페이스 이하경로에 있다. 내 경우엔 프로젝트 이름이 BBS라서 들어간 것이고 아니라면 해당 프로젝트 명으로 들어가면 된다.
BBS 아래에 upload 폴더를 만들어 준다.
BBS/upload

매번 글을 쓸 때마다 폴더가 없다면 만들어 주기 위해 다음과 같이 targetDir.mkdirs();을 넣어준다.

자꾸 오류가 나서 확인해보니 지난번 삭제된 글을 포함해서 게시판 글자 수를 세느라 다음 버튼이 데이터가 없는데도 생성이 되길래 getNext(); 를 유효한 글들만으로 바꾸었던게 문제가 되었다.

그래서 글을 새로 만들 때에는 getNewNext()함수를 쓰도록 새로 만들어 주었다.

BbsDAO.java

	public int getNewNext() {
		String SQL = "SELECT bbsID FROM BBS ORDER BY bbsID DESC";
		try {
			PreparedStatement pstmt = conn.prepareStatement(SQL);
			rs = pstmt.executeQuery();
			
			if(rs.next()) {
				return rs.getInt(1)+1;
			}
			return 1;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return -1;
	}

이렇게 추가해주고 write 함수에 getNewNext로 대체해 준다.

다음과 같이 잘 올라간다.

2. 다운로드 구현

이번엔 다운로드를 구현해보자

다운로드는 어떻게 구현해야 할까

우선 view.jsp파일에서 클릭을 했을 시 다운로드 액션을 할 수 있도록 해주어야 한다.

String directory = application.getRealPath("/upload/"+bbsID+"/");
				
File targetDir = new File(directory);
	if(!targetDir.exists()){
		targetDir.mkdirs();
			}
			
	String files[] = new File(directory).list();	
				
	for(String file : files){
					
	out.write("<a href=\"" + request.getContextPath() + "/downloadAction?bbsID="+bbsID+"&file="+
		java.net.URLEncoder.encode(file,"UTF-8") + "\">" + file + "</a><br>");
			}

다음과 같이 해당 글을 클릭했을 때 upload/글ID/ 경로 폴더로 들어가 존재하는 파일들을 불러오도록 배열을 구현해준다.

이후에 클릭하면 a href를 이용해 downloadAction으로 넘어가면서 get방식으로 bbsID 변수를 넘겨주고, file=파일명 인코딩 해서 넘겨주도록 한다.

소스파일 아래에 new -> servlet 파일을 추가해
downloadAction을 만들어준다.

downloadAction.java

package file;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import bbs.Bbs;

/**
 * Servlet implementation class downloadAction
 */
@WebServlet("/downloadAction")
public class downloadAction extends HttpServlet {
	private static final long serialVersionUID = 1L;
	
	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		
String fileName = request.getParameter("file");
		
		int bbsID = 0;
		
		if (request.getParameter("bbsID")!=null){
			bbsID = Integer.parseInt(request.getParameter("bbsID"));
		}
		
		

		String directory = this.getServletContext().getRealPath("/upload/");
		File file = new File(directory +"/" + bbsID + "/" + fileName);
		
		String mimeType = getServletContext().getMimeType(file.toString());
		if(mimeType == null) {
			response.setContentType("application/octet-stream");
		}
		
		String downloadName = null;
		
		if (request.getHeader("user-agent").indexOf("MSIE")== -1) {
			downloadName = new String(fileName.getBytes("UTF-8"),"8859_1");
		}else {
			downloadName = new String(fileName.getBytes("EUC-KR"),"8859_1");
		}
		
		response.setHeader("Content-Disposition", "attachment;filename=\""
		+ downloadName + "\";");
		
		FileInputStream fileInputStream = new FileInputStream(file);
		ServletOutputStream servletOutputStream = response.getOutputStream();
		
		byte b[] = new byte[1024];
		int data = 0;
		
		while((data = (fileInputStream.read(b,0,b.length))) != -1) {
			servletOutputStream.write(b,0,data);
		}
		
		servletOutputStream.flush();
		servletOutputStream.close();
		fileInputStream.close();
			
	}
}

bbsID를 넘겨주는 이유는 다음과 같이 하위 디렉토리를 찾아가기 위함이다.

String directory = this.getServletContext().getRealPath("/upload/");
File file = new File(directory +"/" + bbsID + "/" + fileName);

다음으로
Mimetype이란?

MIME이란? Multipurpose Internet Mail Extensions의 약자로 간략히 말씀을 드리면 파일 변환을 뜻한다고할 수 있습니다.

MIME을 사용하기전에는 UUEncode 방식을 이용하고 있었으며 UUEncode에는 치명적인 단점이 있었습니다 그러한 담점을 보강하여 새로운 인코딩 방식이 등장하게 되었으니 이것을 MIME이라고 합니다.

예전에는 텍스트파일을 주고받는데 ASCII로 공통된 표준에 따르기만하면 문제가 없었습니다 하지만 네트워크를 통해 ASCII 파일이 아닌 바이너리 파일을 보내는 경우가 생기게 되었습니다 이러한 바이너리파일에는 음악파일, 무비파일, 워드파일 등등의 문서를 지칭하는 것입니다.
하지만 ASCII만으로는 전송이 불가능하여 이러한 바이너리 파일들을 기존의 시스템에서 문제 없이 전달하기 위해서는 텍스트파일로 변환이 필요하게 되었습니다
이러한 텍스트파일로 변환을 인코딩(Encoding)이라고하며, 텍스트 파일을 바이너리 파일로 변환하는 과정을 디코딩(Decoding)이라고 합니다.

어쨌든 파일을 데이어로 변환시켜준다고 이해하고 넘어가자

if (request.getHeader("user-agent").indexOf("MSIE")== -1) {
			downloadName = new String(fileName.getBytes("UTF-8"),"8859_1");
		}else {
			downloadName = new String(fileName.getBytes("EUC-KR"),"8859_1");
		}
		
		response.setHeader("Content-Disposition", "attachment;filename=\""
		+ downloadName + "\";");

해당 부분은 MicroSoft InternetExplorer(MSIE)가 아닌경우 데이터 처리를 해주는 부분이다.

8859_1 은 데이터 인코딩 방식이라고 이해하고 넘어가자.

이하는 파일을 다운로드 하기 위한 마무리 코딩이다.

좌측 하단에 다음과 같이 파일 다운로드가 실행된다.

profile
https://github.com/jsw4215

0개의 댓글