28 분 소요

파일 업로드

  • Part API를 이용하여 파일 업로드를 구현하였다.

단일 파일 최대 용량 설정 (Nginx)

  • /etc/nginx/nginx.conf 에서 단일 파일 최대 용량을 500MB로 설정하였다.
... 생략 ...
http {
        client_max_body_size 500M;
        ... 생략...
}
  • 업로드 할 단일 파일 크기가 500MB를 넘으면 오류가 발생한다.
    Pasted image 20230524223827


게시글 새로 저장

JSP

설명
  • file 선택을 위한 input 태그에서 type 속성 값을 file로 설정한다.
  • form 태그에서 enctype 속성 값을 multipart/form-data로 해준다. 대용량 파일을 서버로 보내기 위한 설정이다.
  • 한 게시물당 최대 3개 까지 첨부파일을 저장할 수 있지만 아직 한 게시물에 동시에 파일 추가되는 기능은 구현하지 않았다.
과정

  1. 사용자가 글쓰기 버튼을 클릭한다.
    Pasted image 20230524224712
  2. 제목과 본문을 작성한다. 그리고 파일선택 버튼을 클릭하여 업로드할 파일을 선택하여준다.
    Pasted image 20230524225111
    Pasted image 20230524224837
  3. 게시글 작성이 완료되고 저장 버튼을 누르면 게시글과 첨부파일이 서버로 전송되어 저장된다.
    Pasted image 20230524225050
  4. 게시글 확인
    Pasted image 20230524225214
  5. 서버에 파일이 잘 업로드 된 것을 확인할 수 있다.
코드 ( 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=") 을 통해 파일인지 아니면 그냥 텍스트인지 구분한다.
  • 파일이름이 없으면 서버에 파일을 저장하지 않는다.
과정

  1. 사용자가 제목, 본문 그리고 파일을 첨부하여 POST 전송으로 서버에 보낸다.
  2. 제목과 본문을 DB서버에 저장해준다.
  3. 게시물 저장 시간과 게시물 번호를 이용하여 게시문 파일을 저장하기 위한 폴더를 만들어준다.
  4. 폴더에 사용자가 첨부한 파일을 저장한다. 최대 저장가능한 파일 수를 3개로 설정하였다.
  5. 하지만 만약 파일을 서버에 저장 중 오류가 발생한다면 게시글 즉 제목과 본문은 저장되고 파일만 서버에 저장이 안된다.
코드 ( 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개까지 저장이 가능하다.
  • 수정 모드 이면서 첨부 파일이 있으면 첨부파일 링크 및 파일 삭제버튼이 나타난다.
    Pasted image 20230524235849
과정

  1. 자신이 쓴 게시글에서 수정 버튼을 클릭하여 게시글 수정 페이지로 들어간다.
    Pasted image 20230524235819
    Pasted image 20230524235849
  2. 수정 페이지에서 제목과 본문을 변경할 수 있다.
  3. 전에 올렸던 첨부파일 확인이 가능하다. 그리고 이 첨부파일을 삭제버튼을 통해 삭제도 가능하다.
  4. 새로운 파일을 추가할 수 있는 파일 버튼도 존재한다.
  5. 수정이 완료되면 저장 버튼을 눌러준다.
  6. 만약 파일 첨부개수가 3개가 넘으면 새로운 파일은 추가되지 않는다. 하지만 변화된 게시글은 저장이 된다.
    Pasted image 20230525000056
    Pasted image 20230525000111
첨부파일 삭제 과정
  1. 첨부파일의 삭제 버튼을 눌러준다.
    Pasted image 20230525003215
  2. javacript 코드의 fetch를 통해 첨부파일 삭제 get 요청이 보내진다.
  3. 첨부파일이 서버에서 정상적으로 삭제가 되면 json 형태로 success 응답을 받는다.
    Pasted image 20230525003139
  4. 성공적으로 삭제가 되면 첨부파일 링크를 테이블에서 삭제한다.
    Pasted image 20230525003233
코드 ( 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서버에 저장한다.
  • 사용자가 파일 삭제 요청을 하면 웹 서버에 저장된 첨부파일을 삭제한다.
과정

  1. 사용자가 게시글을 수정 및 파일을 추가 하여 서버에 수정본 저장 요청을 보낸다.
  2. 서버는 옛 게시글을 삭제한다.
  3. 서버는 수정된 제목과 본문을 DB 서버에 새롭게 저장한다.
  4. 옛 게시글의 첨부파일이 들어있는 폴더 명을 새로운 게시글과 연관된 폴더명으로 변경해 준다. ( 파일명 : 게시글생성일_게시글번호 )
  5. 새로운 첨부파일이 있으면 웹 서버의 게시글 관련 폴더에 저장해 준다.
  6. 새로운 개시글과 첨부파일 저장이 완료되면 사용자에게 성공 응답을 한다.
첨부파일 삭제과정

  1. 사용자는 javascript의 fetch 함수를 통해 첨부파일 삭제 get 요청을 보낸다.
  2. 요청을 보낼 때 파라미터로 게시글 번호와 첨부파일 명을 보내준다.
  3. 서버에서 게시글 번호와 첨부파일 명을 받아 게시글 작성자와 요청을 보낸 사용자가 일치하는지 확인한다.
  4. 일치하면 존재하는 첨부파일인지 확인한다.
  5. 파일이 존재하면 저장된 파일을 삭제한다.
  6. json 형태로 사용자에게 파일 삭제 성공 하였다고 응답한다. (success)
게시글 삭제 과정

  1. 사용자는 자기가 쓴 글 삭제요청을 보낸다.
  2. DB에 저장된 게시글을 삭제한다.
  3. 파일 업로드 폴더를 삭제한다.
  4. 삭제완료 응답을 사용자에게 보낸다.
코드 (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

설명
  • 게시글 페이지 요청이 들어오면 첨부파일 링크도 추가한 게시글 페이지를 보여준다.
    Pasted image 20230525030427
  • 게시글에 업로드 된 파일의 다운로드 링크를 만들어준다.
  • 다운로드 링크를 누르면 서버에 저장된 파일을 다운받을 수 있다.
  • 첨부파일이 없을 경우 링크는 나타나지 않는다.
과정

  1. 사용자가 첨부파일 다운로드 링크를 클릭한다.
    Pasted image 20230525025925
  2. 사용자는 서버로 파일 다운로드 get 요청을 보낸다.
  3. 서버는 사용자에게 파일을 버퍼로 쪼개어 응답을 보내준다.
  4. 다운로드 완료
    Pasted image 20230525025952
코드 ( 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

설명
  • 게시글 페이지 요청이 들어오면 첨부파일 링크도 추가한 게시글 페이지를 보여준다.
    Pasted image 20230525030435
  • 다운로드 요청이 들어오면 서버에서 파일을 버퍼 단위로 쪼개어 사용자에게 전송한다.
  • 다운로드 파일이 없으면 404 페이지를 보여준다.
과정

  1. 사용자가 다운로드 링크를 눌러 서버에 파일 다운로드 요청을 보낸다.
  2. 서버는 가장먼저 서버에 파일이 존재하는지 확인한다.
  3. 서버는 어느 브라우저에서 온 요청인지 확인한다.
  4. 위에서 알아본 정보를 가지고 응답 헤더를 작성한다.
  5. 서버는 파일을 버퍼 단위로 나누어 사용자에게 전달한다.
코드 (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 컬럼) 게시판 테이블에 추가.
    Pasted image 20230525034420
alter table notice_board add views number(20) default 0 not null;

# 조회수 값 1 증가 하기.
update notice_board set views = 1 where sid=101;

JSP

설명

  • 사용자가 게시글 방문하면 게시글 조회수는 1 증가한다.
  • 사용자는 게시글의 조회수를 확인할 수 있다.
    Pasted image 20230525041326

과정

  1. 사용자는 서버로 게시글 페이지를 요청한다.
  2. 서버에서 게시글 조회수를 1 증가 시킨다.
  3. 게시글 페이지를 완성하여 사용자에게 전달한다.

코드 ( notice.jsp )

  • 조회수 가져오기
  • notice는 servlet 에서 가져온 것이다.
<%=notice.getViews() %>

Servlet

설명

  • 사용자가 게시글에 방문하면 table의 조회수 값을 1증가 시킨다.

과정

  1. 사용자는 서버로 게시글 페이지를 요청한다.
  2. 서버는 DB 서버의 게시판 테이블에 조회수 값을 1증가 시켜 저장한다.
  3. 서버는 게시글 페이지를 완성하여 사용자에게 전달한다.

코드 ( 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;
	...
}

후기

  • 직접 업로드와 다운로드 기능을 만들면서 어떤 방식으로 동작하는지 알 수 있는 계기가 되었다. 특히 다운로드 기능을 구현할 때 인코딩 에러가 발생하여 당황스러웠지만 응답 헤더를 고치니 해결되어 다행이었다.
  • 조회수를 구현은 생각보다 간단하였다.

참고 사이트

댓글남기기