웹 서버 만들기(13) 업로드 다운로드 조회수
파일 업로드
- Part API를 이용하여 파일 업로드를 구현하였다.
 
단일 파일 최대 용량 설정 (Nginx)
/etc/nginx/nginx.conf에서 단일 파일 최대 용량을 500MB로 설정하였다.
... 생략 ...
http {
        client_max_body_size 500M;
        ... 생략...
}
- 업로드 할 단일 파일 크기가 500MB를 넘으면 오류가 발생한다.
 
게시글 새로 저장
JSP
설명
- file 선택을 위한 
input태그에서 type 속성 값을file로 설정한다. form태그에서 enctype 속성 값을multipart/form-data로 해준다. 대용량 파일을 서버로 보내기 위한 설정이다.- 한 게시물당 최대 3개 까지 첨부파일을 저장할 수 있지만 아직 한 게시물에 동시에 파일 추가되는 기능은 구현하지 않았다.
 
과정
- 사용자가 글쓰기 버튼을 클릭한다.
 - 제목과 본문을 작성한다. 그리고 파일선택 버튼을 클릭하여 업로드할 파일을 선택하여준다.
 - 게시글 작성이 완료되고 저장 버튼을 누르면 게시글과 첨부파일이 서버로 전송되어 저장된다.
 - 게시글 확인 
 - 서버에 파일이 잘 업로드 된 것을 확인할 수 있다.
 
코드 ( notice_write.jsp )
- form 의 enctype 속성을 추가하였고, input의 type 속성을 
file로 해주었다. 
... 생략 ...
<form method="post" action="${noticeBoardURL}" enctype="multipart/form-data">
... 생략 ...
<input type="file" name="UPLOAD_FILE"/>
... 생략 ...
Servlet
설명
- 사용자가 전송한 제목, 본문은 DB서버에 저장하고 파일은 웹 서버에 저장한다.
 - POST 요청으로 들어온다.
 - 최대 3개의 파일 까지 서버에 저장이 가능하다.
 part.getHeader("Content-Disposition").contains("filename=")을 통해 파일인지 아니면 그냥 텍스트인지 구분한다.- 파일이름이 없으면 서버에 파일을 저장하지 않는다.
 
과정
- 사용자가 제목, 본문 그리고 파일을 첨부하여 POST 전송으로 서버에 보낸다.
 - 제목과 본문을 DB서버에 저장해준다.
 - 게시물 저장 시간과 게시물 번호를 이용하여 게시문 파일을 저장하기 위한 폴더를 만들어준다.
 - 폴더에 사용자가 첨부한 파일을 저장한다. 최대 저장가능한 파일 수를 3개로 설정하였다.
 - 하지만 만약 파일을 서버에 저장 중 오류가 발생한다면 게시글 즉 제목과 본문은 저장되고 파일만 서버에 저장이 안된다.
 
코드 ( MainPage.java )
- 사용자가 업로드한 파일을 웹 서버에 바로 저장해 준다.
 
private final int MAX_FILE_NUM = 2; // 최대 저장 가능한 파일수-1
public class MainPage extends HttpServlet{
	... 생략 ...
	private boolean uriSearch(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
	    ... 생략 ...
		// 글 저장
        if(request.getMethod().equals("POST") && uri.equals(noticeBoardSave)){
            var charSet = "utf-8";
            request.setCharacterEncoding(charSet);
            if(!request.getContentType().toLowerCase().startsWith("multipart/form-data")){
                //404 에러 발생
                // application/x-www-form-urlencoded 타입이 아니라.
                // 오직 multipart/form-data 타입만 받는다.
                return false;
            }
            ... 생략 ...
        
	        // 제목 본문 DB에 저장.
	        var user = (User)request.getAttribute("user");
            var notice = new NoticeBoardDAO(getServletContext());
            var genTime = System.currentTimeMillis(); // 게시글 생성 시각
            var noticeSid = notice.saveNotice(user, title, mainText,genTime);
            if(noticeSid == 0){ // db 저장 실패
                result = "<script>alert('fail');location.href='"+noticeBoardWrite+"'</script>";
                simplePage(response, result);
                return true;
            }
            
            // ***** 파일 업로드 시작 *****
            var noticeRe = "no file";
            try{
                // 폴더 생성 또는 존재 확인
                // 저장을 위한 폴더
                var folderName = genTime+"_"+ noticeSid;
                var uploadPath = getFilePath(request, folderName);
                // genTime + _ + noticeSID => 폴더명
                var parts = request.getParts();
                for(var part:parts){
                    if(!part.getHeader("Content-Disposition").contains("filename=")){
                        continue;
                    }
                    if(part.getSubmittedFileName().equals("")){
                        continue;
                    }
                    var file = new File(uploadPath);
                    if(!file.exists()){
                        // 폴더 생성
                        file.mkdir();
                        System.out.println(noticeSid+"게시글 폴더 생성 함");
                    }
                    var count = file.listFiles().length;
                    if(count > MAX_FILE_NUM){
                        System.out.println("최대 파일 갯수 초과");
                        break;
                    }
                    System.out.println("퐅더 존재? => "+file.isDirectory());
                    System.out.println("타입 : "+part.getHeader("Content-Disposition"));
                    System.out.println("크기 : "+part.getSize());
                    System.out.println("이름 : "+part.getSubmittedFileName());
                   part.write(uploadPath+File.separator+part.getSubmittedFileName());
                    part.delete(); // 임시파일 삭제
  
                    System.out.println("업로드 성공");
                    noticeRe = "file upload success";
                    System.out.println("경로 : "+uploadPath);
                }
            }catch(Exception e){
                e.printStackTrace();
                System.out.println("파일 업로드 실패");
                noticeRe = "file upload fail";
            }
            // ***** 파일 업로드 end *****
            result = "<script>alert('success "+noticeRe+"');location.href='"+noticeBoard+"?page=1&q='</script>";
            simplePage(response, result);
            return true;
        }
... 생략 ...
선택
선택 1. web.xml
request.getParts()를 사용하기 위해서는 임시저장 폴더를 만들어 줘야 한다.file-size-threshold에 설정한 용량이 넘으면 임시저장 폴더에 저장한다는 것이다.- 코드에서 실제 파일이 어디에 저장될 것인지 코딩해 줘야한다.
 
<servlet>
	<servlet-name>mainpage</servlet-name>
	<servlet-class>com.mingyu2.happyhackingmain.MainPage</servlet-class>
	<multipart-config>
		<location>/home/mq/temp</location>
		<max-file-size>-1</max-file-size>
		<max-request-size>-1</max-request-size>
		<file-size-threshold>1024</file-size-threshold>
	</multipart-config>
</servlet>
선택 2. annotation 적기.
- 각 클래스마다 적어주어야 한다.
 - location 설정을 따로 안하면 자바가 지정해놓은 임시 저장소를 이용한다.
 
@MultipartConfig(
    location = "/home/mq/temp",
    maxFileSize = -1,
    maxRequestSize = -1,
    fileSizeThreshold = 1024
)
public class MainPage extends HttpServlet{
게시글 수정 및 저장 & 파일 삭제
JSP
설명
- 게시글 수정 상태에서 첨부파일을 다운 받을 수 있는 링크를 추가하였다.
 - 첨부파일 삭제 버튼을 추가하였다. 삭제 버튼을 누르면 첨부파일이 바로 삭제된다.
 fetch를 이용하여 get 전송을 통해 첨부파일을 삭제하여 준다.- 새로운 첨부파일을 추가하고 저장버튼을 누르면 파일은 서버에 저장된다. 하지만 한 게시글 당 파일은 최대 3개까지 저장이 가능하다.
 - 수정 모드 이면서 첨부 파일이 있으면 첨부파일 링크 및 파일 삭제버튼이 나타난다.
 
과정
- 자신이 쓴 게시글에서 수정 버튼을 클릭하여 게시글 수정 페이지로 들어간다.
 - 수정 페이지에서 제목과 본문을 변경할 수 있다.
 - 전에 올렸던 첨부파일 확인이 가능하다. 그리고 이 첨부파일을 삭제버튼을 통해 삭제도 가능하다.
 - 새로운 파일을 추가할 수 있는 
파일 버튼도 존재한다. - 수정이 완료되면 
저장버튼을 눌러준다. - 만약 파일 첨부개수가 3개가 넘으면 새로운 파일은 추가되지 않는다. 하지만 변화된 게시글은 저장이 된다.
 
첨부파일 삭제 과정
- 첨부파일의 삭제 버튼을 눌러준다.
 - javacript 코드의 fetch를 통해 첨부파일 삭제 get 요청이 보내진다.
 - 첨부파일이 서버에서 정상적으로 삭제가 되면 json 형태로 
success응답을 받는다. - 성공적으로 삭제가 되면 첨부파일 링크를 테이블에서 삭제한다.
 
코드 ( notice_write.jsp )
- servlet 에서 파일이름 목록과 모드 정보를 받아온다.
 
<%@ page import="com.mingyu2.happyhackingmain.Pair" %>
<%@ page import="java.util.ArrayList" %>
<%
var mode = (Boolean)request.getAttribute("modifyMode");
if(mode == null){
    mode = false;
}
var fileNameList = (ArrayList<Pair<String,String>>)request.getAttribute("fileNameList");
%>
- 수정 모드 & 첨부파일이 하나 이상이면 다운 링크 및 삭제 버튼을 표시해 준다.
 
<% if(mode && fileNameList.size() > 0) { %>
<% for(var file : fileNameList ) { %>
    <tr id='<%=file.getD1() %>'>    
    <td colspan="2">
            <a href="${noticeBoardFileDownload}?downlink=<%=file.getD2() %>"><%=file.getD1() %></a>
            <a href="javascript:delete_file('<%=file.getD1() %>')">delete</a>
    </td>
    </tr>
<% } %>
<% }%>
<tr>
<td colspan="2">
    <input type="file" name="UPLOAD_FILE"/>
</td>
</tr>
- 첨부파일 삭제 버튼을 누르면 
delete_file함수가 동작한다. - 파라미터로 첨부파일 이름을 가져온다.
 
<% if(mode) { %>
<% if(fileNameList.size() > 0) { %>
<script>
    function delete_file(name){
        var url = "${noticeBoardFileDelete}?noticeID=${noticeID}&filename="+name;
        var re = confirm("삭제 하시겠습니까?");
        if(!re){
            return;
        }
        fetch(url)
            .then((response)=> response.json())
            .then((json)=>{
                var j = JSON.stringify(json);
                var re = JSON.parse(j).result;
                if(re == "success"){
                    var table = document.getElementsByClassName("notice_write")[0];
                    for(var i = 0;i<table.rows.length;i++){
                        if(table.rows[i].id == name){
                            table.deleteRow(i);
                            break;
                        }
                    }
                    alert("파일 삭제 성공");
                }else{
                    alert("파일 삭제 실패");
                }
                console.log(re);
            });
    }
</script>
<% } %>
<% } %>
- JSON.stringify(xx) : 자바 스크립트 객체를 Json data로 변환한다.
 - JSON.parse(xx) : Json data를 자바 스크립트 객체로 변환한다.
 
Servlet
설명
- 자기가 쓴 게시글에서 삭제하기 버튼을 누르면 DB에 저장된 게시글이 삭제되고, 웹 서버에 저장된 첨부파일도 삭제된다.
 - 사용자가 전송한 수정된 게시글을 DB서버에 저장한다.
 - 사용자가 파일 삭제 요청을 하면 웹 서버에 저장된 첨부파일을 삭제한다.
 
과정
- 사용자가 게시글을 수정 및 파일을 추가 하여 서버에 수정본 저장 요청을 보낸다.
 - 서버는 옛 게시글을 삭제한다.
 - 서버는 수정된 제목과 본문을 DB 서버에 새롭게 저장한다.
 - 옛 게시글의 첨부파일이 들어있는 폴더 명을 새로운 게시글과 연관된 폴더명으로 변경해 준다. ( 파일명 : 게시글생성일_게시글번호 )
 - 새로운 첨부파일이 있으면 웹 서버의 게시글 관련 폴더에 저장해 준다.
 - 새로운 개시글과 첨부파일 저장이 완료되면 사용자에게 성공 응답을 한다.
 
첨부파일 삭제과정
- 사용자는 javascript의 fetch 함수를 통해 첨부파일 삭제 get 요청을 보낸다.
 - 요청을 보낼 때 파라미터로 게시글 번호와 첨부파일 명을 보내준다.
 - 서버에서 게시글 번호와 첨부파일 명을 받아 게시글 작성자와 요청을 보낸 사용자가 일치하는지 확인한다.
 - 일치하면 존재하는 첨부파일인지 확인한다.
 - 파일이 존재하면 저장된 파일을 삭제한다.
 - json 형태로 사용자에게 파일 삭제 성공 하였다고 응답한다. (success)
 
게시글 삭제 과정
- 사용자는 자기가 쓴 글 삭제요청을 보낸다.
 - DB에 저장된 게시글을 삭제한다.
 - 파일 업로드 폴더를 삭제한다.
 - 삭제완료 응답을 사용자에게 보낸다.
 
코드 (MainPage.java)
- 게시글 삭제
 - 업로드 한 파일을 저장하기 위한 폴더를 삭제해 준다.
 
public class MainPage extends HttpServlet{
	... 생략 ...
	private boolean uriSearch(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
	    ... 생략 ...
        // 글 삭제하기
        if(request.getMethod().equals("GET") && uri.equals(noticeBoardDelete)){
            try {
               ... 생략 ...
                // *** 파일 업로드 폴더 삭제 ***
                var folderName = notice.getGenTime()+"_"+notice.getSid();
                var uploadPath = getFilePath(request, folderName);
                var folder = new File(uploadPath);
                if(folder.exists()){
                    var files = folder.listFiles();
                    // 모든 파일 삭제
                    for(var file : files){
                        file.delete();
                    }
                    // 마지막으로 폴더 삭제
                    folder.delete();
                }
                // *** 파일 업로드 폴더 삭제 end ***
                result = "<script>alert('success');location.href='"+noticeBoard+"'</script>";
                simplePage(response, result);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
- 첨부파일 삭제
 - 일치하는 파일 하나만을 삭제한다.
 
public class MainPage extends HttpServlet{
	... 생략 ...
	private boolean uriSearch(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
	    ... 생략 ...
        // 파일 삭제
        if(request.getMethod().equals("GET") && uri.equals(noticeBoardFileDelete)){
            var re = "fail";
            try{
                var noticeID = request.getParameter("noticeID");
                var filename = request.getParameter("filename");
                var sid = Long.parseLong(noticeID);
                var noticeDAO = new NoticeBoardDAO(getServletContext());
                var notice = noticeDAO.getNotice(sid);
  
                if(notice == null) {
                    System.out.println("존재하지 않는 게시글");
                    simplePage(response, "{\"result\":\"fail\"}");
                    return true;
                }
  
                var user = (User)request.getAttribute("user");
                if(user.getSid() != notice.getUserSID()){ // 글 작성자와 현재 유저와 일치안함 실패
                    System.out.println("글 작성자와 현재유저 일치 안함");
                    simplePage(response, "{\"result\":\"fail\"}");
                    return true;
                }
  
                // 파일 존재 여부 확인
                var folderName = notice.getGenTime()+"_"+notice.getSid();
                var filePath = getFilePath(request, folderName+File.separatorChar+filename);
                var file = new File(filePath);
                if(!file.exists()){
                    System.out.println("파일 존재 안함!");
                    simplePage(response, "{\"result\":\"fail\"}");
                    return true;
                }
                if(!file.isFile()){
                    System.out.println("파일이 아님!");
                    simplePage(response, "{\"result\":\"fail\"}");
                    return true;
                }
  
                // 파일 삭제 하기.
                if(file.delete()){
                    re = "success";
                }
            }catch(Exception e){
                e.printStackTrace();
                re = "fail";
            }
            simplePage(response, "{\"result\":\""+re+"\"}");
            return true;
        }
- 수정 게시글 저장하기
 - 폴더 명을 변경하여 변경한 폴더에 사용자가 보낸 파일을 저장한다.
 
public class MainPage extends HttpServlet{
	... 생략 ...
	private boolean uriSearch(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
	    ... 생략 ...
        // 글 수정 저장하기
        if(request.getMethod().equals("POST") && uri.equals(noticeBoardModifySave)) {
            try {
            ... 생략 ...
                // 전 저장위치
                var noticeSID = notice.getSid();
                var beforeFolderName = notice.getGenTime()+"_"+noticeSID;
                var beforeUploadPath = getFilePath(request, beforeFolderName);
                
                // 글 삭제
                if(!noticeDAO.deleteNotice(sid)){ // 삭제 실패
                    return false;
                }
                // 새로운 글 저장
                var genTime = System.currentTimeMillis();
                var newSid = noticeDAO.saveNotice(user, title, mainText, genTime);
                if(newSid==0){ // 실패
                    result = "<script>alert('fail');location.href='"+noticeBoard+"'</script>";
                    simplePage(response, result);
                    return true;
                }
                  
                var noticeRe = "no new file";
                try {
                    var folder = new File(beforeUploadPath); // 폴더
                    var newFolderName = genTime+"_"+newSid;
                    var afterUploadPath = getFilePath(request, newFolderName);
                    var newFolder = new File(afterUploadPath);
                    if(folder.exists()){
                        // 폴더 이름만 변경.
                        folder.renameTo(newFolder);
                    }
                    
                    var parts = request.getParts();
                    for(var part:parts){
                        if(!part.getHeader("Content-Disposition").contains("filename=")){
                            continue;
                        }
                        if(part.getSubmittedFileName().equals("")){
                            continue;
                        }
                        if(!newFolder.exists()){
                            // 폴더 생성
                            newFolder.mkdir();
                            System.out.println(newSid+"게시글 폴더 생성 함");
                        }
                        var count = newFolder.listFiles().length;
                        System.out.println("파일 갯수 : "+count);
                        if(count > MAX_FILE_NUM){
                            System.out.println("최대 파일 갯수 초과");
                            noticeRe = "out of file number ( maxcount 3 )";
                            break;
                        }
                        System.out.println("퐅더 존재? => "+newFolder.isDirectory());
                        System.out.println("타입 : "+part.getHeader("Content-Disposition"));
                        System.out.println("크기 : "+part.getSize());
                        System.out.println("이름 : "+part.getSubmittedFileName());
                        part.write(afterUploadPath+File.separator+part.getSubmittedFileName());
                        part.delete(); // 임시 파일 삭제
                        System.out.println("파일 저장 성공");
                        noticeRe = "file upload success";
                        System.out.println("경로 : "+afterUploadPath);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    noticeRe = "file upload fail";
                }
                // 성공
                result = "<script>alert('success : "+noticeRe+"');location.href='"+noticeBoard+"'</script>";
                simplePage(response, result);
                return true;
            }catch(Exception e){
                e.printStackTrace();
                return false;
            }
        }
파일 다운로드
- MIME를 이용하여 대용량 파일을 텍스트 문자 인코딩하여 전송한다.
 
게시판 첨부파일 다운로드
JSP
설명
- 게시글 페이지 요청이 들어오면 첨부파일 링크도 추가한 게시글 페이지를 보여준다.
 - 게시글에 업로드 된 파일의 다운로드 링크를 만들어준다.
 - 다운로드 링크를 누르면 서버에 저장된 파일을 다운받을 수 있다.
 - 첨부파일이 없을 경우 링크는 나타나지 않는다.
 
과정
- 사용자가 첨부파일 다운로드 링크를 클릭한다.
 - 사용자는 서버로 파일 다운로드 get 요청을 보낸다.
 - 서버는 사용자에게 파일을 버퍼로 쪼개어 응답을 보내준다.
 - 다운로드 완료
 
코드 ( notice.jsp )
- 파일 이름과 파일 위치 배열
 - servlet으로 부터 배열을 받아온다.
 
<%
var fileNameList = (ArrayList<Pair<String,String>>)request.getAttribute("fileNameList");
%>
- 게시글 파일 다운로드 링크
 
<% if(fileNameList.size() > 0) { %>
<tr>
    <td style="text-align: center;">
        <span>첨부파일</span>
    </td>
    <td>
        <% for(var file : fileNameList ) { %>
            <a href="${noticeBoardFileDownload}?downlink=<%=file.getD2() %>"><%=file.getD1() %></a><br>
        <% } %>
    </td>
</tr>
<% } %>
Servlet
설명
- 게시글 페이지 요청이 들어오면 첨부파일 링크도 추가한 게시글 페이지를 보여준다.
 - 다운로드 요청이 들어오면 서버에서 파일을 버퍼 단위로 쪼개어 사용자에게 전송한다.
 - 다운로드 파일이 없으면 404 페이지를 보여준다.
 
과정
- 사용자가 다운로드 링크를 눌러 서버에 파일 다운로드 요청을 보낸다.
 - 서버는 가장먼저 서버에 파일이 존재하는지 확인한다.
 - 서버는 어느 브라우저에서 온 요청인지 확인한다.
 - 위에서 알아본 정보를 가지고 응답 헤더를 작성한다.
 - 서버는 파일을 버퍼 단위로 나누어 사용자에게 전달한다.
 
코드 (MainPage.java)
- 게시글 파일 다운로드
 - 사용자 브라우저가 모질라일 때만 다운로드가 가능하다.
 - 헤더에 파일 사이즈를 입력하면 브라우저가 다운완료 시간을 예측한다.
 
public class MainPage extends HttpServlet{
	... 생략 ...
	private boolean uriSearch(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
	    ... 생략 ...
        // 게시글 파일 다운로드
        if(uri.equals(noticeBoardFileDownload)){
            var downlink = request.getParameter("downlink");
            var filePath = new String(getFilePath(request, downlink).getBytes("UTF-8")); // utf-8로 바꿔준다.
  
            var file = new File(filePath);
            if(!file.exists()){
                return false;
            }
            if(!file.isFile()){
                return false;
            }
  
            var filesize = file.length();
            var sMimeType = getServletContext().getMimeType(filePath); // 확장자에 따라 달라진다.
            if(sMimeType == null || sMimeType.length() == 0){
                sMimeType = "application/octet-stream";
            }
            BufferedInputStream fin = null;
            BufferedOutputStream outs = null;
            try {
                var fileName= downlink.split("/")[1];
                byte[] b = new byte[8192];
  
                response.setContentType(sMimeType+"; charset=utf-8");
                var userAgent = request.getHeader("User-Agent");
                System.out.println(userAgent);
                if(userAgent != null && userAgent.contains("MSIE 5.5")){ // MSIE 5.5 이하
                    return false;
                }else if(userAgent != null && userAgent.contains("MSIE")){ // MS IE
                    return false;
                }else{ // 모질라
                    response.setHeader("Content-Disposition", "attachment; filename="+ new String(fileName.getBytes("utf-8"), "latin1") + ";");
                }
                // 파일 사이즈 정확히 알아야함
                if(filesize > 0){
                    response.setHeader("Content-Length", ""+filesize);
                }
                fin = new BufferedInputStream(new FileInputStream(file));
                outs = new BufferedOutputStream(response.getOutputStream());
                int read = 0;
                while((read= fin.read(b)) != -1){
                    outs.write(b, 0, read);
                }
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            } finally {
                try{
                    fin.close();
                }catch(Exception e){}
                try{
                    outs.close();
                }catch(Exception e){}
            }
            return true;
        }
- 글 자세히 보기
 - href 링크를 만들어 jsp 에 전달해 준다.
 
public class MainPage extends HttpServlet{
	... 생략 ...
	private boolean uriSearch(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
	    ... 생략 ...
        // 글 자세히 보기
        if(request.getMethod().equals("GET") && uri.equals(noticeBoardDetail)){
            try {
                ... 생략 ...
                // *** 파일들 읽어 오기 ***
                // href 링크 만들기
                var folderName = notice.getGenTime()+"_"+noticeSID;
                var uploadPath = getFilePath(request, folderName);
                // 파일 다운로드 get 파라미터 이름
                var fileNameList = new ArrayList<Pair<String,String>>();
                try{
                    var folder = new File(uploadPath); // 폴더
                    if(folder.exists()){
                        var files = folder.listFiles(); // 파일들
                        for(var file : files) {
                            var fileName = file.getName();
                            var hrefPrameter = folderName+"%2F"+fileName;
                            fileNameList.add(new Pair<String,String>(fileName,hrefPrameter));
                            System.out.println(fileNameList);
                        }
                    }
                }catch(Exception e){
                    e.printStackTrace();
                }
                request.setAttribute("fileNameList", fileNameList);
                request.setAttribute("noticeBoardFileDownload", noticeBoardFileDownload);
                // *** 파일들 읽어 오기 end ***
                request.setAttribute("notice", notice);
                if(user.getSid() == notice.getUserSID()){
                    request.setAttribute("modifyButton", true);
                    request.setAttribute("modifyHref", noticeBoardModify);
                    request.setAttribute("deleteHref", noticeBoardDelete);
                }
                gotoForwardPage(request, response, "/WEB-INF/main_page/notice_board/notice_detail.jsp");
                return true;
            }catch(Exception e){
                e.printStackTrace();
                return false;
            }
        }
공통 코드
- MainPage.java 의 getFilePath 함수
 
    private String getFilePath(ServletRequest request,String folderName){
        var path = request.getServletContext().getRealPath("/WEB-INF/upload_folder/"+folderName);
        return path;
    }
게시글 조회수
DB
- 조회수 컬럼(views 컬럼) 게시판 테이블에 추가.
 
alter table notice_board add views number(20) default 0 not null;
# 조회수 값 1 증가 하기.
update notice_board set views = 1 where sid=101;
JSP
설명
- 사용자가 게시글 방문하면 게시글 조회수는 1 증가한다.
 - 사용자는 게시글의 조회수를 확인할 수 있다.
 
과정
- 사용자는 서버로 게시글 페이지를 요청한다.
 - 서버에서 게시글 조회수를 1 증가 시킨다.
 - 게시글 페이지를 완성하여 사용자에게 전달한다.
 
코드 ( notice.jsp )
- 조회수 가져오기
 - notice는 servlet 에서 가져온 것이다.
 
<%=notice.getViews() %>
Servlet
설명
- 사용자가 게시글에 방문하면 table의 조회수 값을 1증가 시킨다.
 
과정
- 사용자는 서버로 게시글 페이지를 요청한다.
 - 서버는 DB 서버의 게시판 테이블에 조회수 값을 1증가 시켜 저장한다.
 - 서버는 게시글 페이지를 완성하여 사용자에게 전달한다.
 
코드 ( MainPage.java )
- 조회수 1 증가 하기
 
public class MainPage extends HttpServlet{
	... 생략 ...
	private boolean uriSearch(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
	    ... 생략 ...
        // 글 자세히 보기
        if(request.getMethod().equals("GET") && uri.equals(noticeBoardDetail)){
            try {
                // 게시글 조회수 1 증가 시키기.
                var noticeSID = notice.getSid();
                noticeDAO.incressViews(noticeSID, notice.getViews());
                notice = noticeDAO.getNotice(noticeSID);
                if(notice == null){
                    return false;
                }
                // 게시글 조회수 증가 end
코드 ( Notice.java)
- 조회수 부분을 추가하였다.
 
public class Notice {
    private long views;
    public Notice(
    ... 생략 ...,
        long views
    ){
    ... 생략 ...
    }
    public long getViews(){
        return views;
    }
... 생략 ...
코드 ( NoticeBoardDAO.java )
- DB의 게시판 테이블에 저장된 조회수 값을 1 증가시킨다.
 
public class NoticeBoardDAO {
... 생략 ...
    // 조회수 1 카운트 증가시키기
    public long incressViews(long sid, long currentViews){
        var query = new StringBuilder();
        query.append("update notice_board set views = ");
        query.append(currentViews+1);
        query.append(" where sid=");
        query.append(sid);
        try{
            Connection();
            var pstmt = conn.prepareStatement(query.toString());
            var re = pstmt.executeUpdate();
            System.out.println(sid+" 글 조회수 업데이트 : "+re);
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            Close();
        }
        return sid;
    }
발생한 오류
- Unable to process parts as no multi-part configuration has been provided
 
var parts = request.getParts();
Unable to process parts as no multi-part configuration has been provided
web.xml 에 <multipart-config> 설정을 해 줘야 한다.
- 413 Request Entity Too Large
 
nginx.conf
http {
	client_max_body_size 6M;
	...
}
후기
- 직접 업로드와 다운로드 기능을 만들면서 어떤 방식으로 동작하는지 알 수 있는 계기가 되었다. 특히 다운로드 기능을 구현할 때 인코딩 에러가 발생하여 당황스러웠지만 응답 헤더를 고치니 해결되어 다행이었다.
 - 조회수를 구현은 생각보다 간단하였다.
 
참고 사이트
- Java 자바로 폴더(디렉토리) 삭제하기(하위파일, 폴더 포함) (tistory.com)
 - Java 자바로 폴더(디렉토리) 생성하기 (tistory.com)
 - Java 파일, 디렉토리 존재 여부 확인하기 - 어제 오늘 내일 (tistory.com)
 - 코끼리를 냉장고에 넣는 방법 :: 서블릿/JSP Servlet 3.0에서 Part API를 통한 파일업로드 구현하기 (tistory.com)
 - How to add file uploads function to a webpage in HTML ? - GeeksforGeeks
 - HTTP GET 방식으로 서버에 요청하기 (작성중) (tistory.com)
 - 자바(JAVA) - 폴더 파일명 변경 (tistory.com)
 - WEBDIR :: 폼 필드(input type=”file”) 디자인 #4 (tistory.com)
 - JavaScript - 파일 용량 제한 - CODE:H (hhyemi.github.io)
 - fetch()로 원격 API 호출하기 (tistory.com)
 - Javascript/Jquery TABLE 행 추가, 삭제, 체크된 table row 삭제 방법 (tistory.com)
 - JavaScript - JSON 데이터 변환, 추출, 원하는 데이터 찾기 (tistory.com)
 - Fetch 사용하기 - Web API MDN (mozilla.org)
 - java:servlet:download 권남 (kwonnam.pe.kr)
 - MIME이란 무엇인가? (tistory.com)
 - nginx 413 Request Entity Too Large 오류 (leocat.kr)
 
댓글남기기