본문 바로가기
다양한 기술들/레거시 Web 실습

[11] 게시판심화2 : 썸네일목록, 글작성(+첨부파일 미리보기)

by 예스p 2023. 1. 16.

사진게시판

파일첨부 기능을 활용하여

파일 첨부시 미리보기가 되는 글쓰기 페이지와

첨부된 파일을 썸네일로 활용하는 목록 페이지를 만들어보자


<< 이전포스팅에서 완료된 요청     |    이번포스팅에서 구현할 요청 >>

기능 url요청 요청시 전달값   응답페이지 또는 url재요청
  /web     index.jsp
로그인요청 /login.me userId=?&userPwd=? 실패시 views/common/errorPage.jsp
성공시 /web  url재요청=> index.jsp
로그아웃요청 /logout.me     /web  url재요청=> index.jsp
회원가입페이지 /enrollForm.me     views/member/memberEnrollForm.jsp
회원가입요청 /insert.me userId=? ... 등  실패시 views/common/errorPage.jsp
성공시 /web  url재요청 => index.jsp
마이페이지요청 /myPage.me   로그인전 /web   url재요청 => index.jsp
로그인후 views.member/myPage.jsp
 정보변경요청

/update.me 

userId=? ... 등 실패시 view/common/errorPage.jsp
성공시 /myPage.me   url재요청 
공지사항 목록 /list.no     views/notice/noticeListView.jsp
공지사항글쓰기 /enrollForm.no     views/noticeEnrollForm.jsp
공지사항등록 /insert.no title=?&content=? 실패시 views/common/errorPage.jsp
성공시 /list.no   url재요청
공지 - 상세페이지 /detail.no no=? (수기기재) 실패시 views/common/errorPage.jsp
성공시 views/notice/noticeDetailView.jsp
공지 - 수정페이지 /updateForm.no no=?   views/notice/noticeUpdateForm.jsp
공지 - 수정요청 /update.no no=?&title=?&content=? 실패시 views/common/errorPage.jsp
성공시 /detail.no?no=XX   url재요청
일반게시판 목록 /list.bo cpage=?   views/board/boardListView.jsp
일반 글쓰기 /enrollForm.bo     views/board/boardEnrollForm.jsp
일반글 등록 /insert.bo   실패시 views/common/errorPage.jsp
성공시 /list.bo   url 재요청
일반 - 상세페이지 /detail.bo no=? 실패시  view/common/errorPage.jsp
성공시 views/board/boardDetailView.jsp
일반 - 수정페이지 /updateForm.bo no=   view/board/boardUpdateForm.jsp
일반 - 수정요청 /update.bo   실패시 view/common/errorPage.jsp
성공시 /detail.bo   url 재요청
사진게시판목록 /list.th     views/board/thumbnailListView.jsp
사진 글쓰기 /enrollForm.th     views/board/thumbnailEnrollForm.jsp
사진글 등록 /insert.th   실패시 views/common/errorPage.jsp
성공시 /list.th   url 재요청

 

 


사진게시글 등록

사진게시판의 경우,

사용자가 사진을 첨부했을때 미리보기 기능이 있으면 유용하다.


 

STEP1) thumbnailEnrollForm.jsp

  • 생성위치 : webProject/src/main/webapp/views/board
  • 사용자가 사진게시글을 올릴 수 있는 글작성 페이지를 구성해보자.
    이때, input type=file 버튼은 숨기고, 사진을 올렸을때 미리보기가 가능하도록 한다.
  • input type file 버튼 숨기기(이미지 영역을 클릭했을 때 파일선택 링크 팝업)
    1) 버튼을 감싸는 div에 style="display:none" 부여
    2) 이미지 태그에 onclick="clickFile(해당 이미지 순번)" 이벤트 부여
    3) clickFile 함수를 정의할때, 넘어온 순번에 해당하는 input type=file 버튼에 click() 이벤트를 발생시킨다.
    >> 본래 file타입의 클릭이벤트는 파일을 선택할 수 있는 창을 띄우는것.
    따라서 이미지 영역을 클릭하는 순간 파일을 선택할 수 있는 창을 띄워진다.
  • 파일선택시 해당 파일 img 태그에 띄우기
    1) input type="file"태그에 onchange="loadImg(this,순번)"을 부여한다.
    >>첨부파일을 등록하거나 변경, 취소할때마다 change 이벤트가 발생하여 함수발동
    >>this는 이벤트가 발생한 요소에 대한 정보를 전달하고, 순번은 그에 맞는 이미지 영역으로 가기위해 전달한다.
    2) 객체.files : 파일이 선택되었을때 객체.files[0]에 선택된 파일의 정보가 담기며, 배열로 반환된다.
    선택된 파일이 있다면 배열의 길이가 1, 없다면 0이다.
    >> if(객체.files.length==1) 로 change가 취소인지 등록인지 판별한다.
    3) const reader = new FileReader
    파일을 읽어들이려면 자바스크립트 객체 FileReader가 필요하다.
    4) reader.readAsDataURL(객체.files[0])
    파일을 읽어들이는 메소드로 내부적으로 파일만의 고유한 url을 부여한다.
    (이미지를 찾을 수 있게 한다)
    5) reader.onload = function(e){...}
    파일 읽어들이는것이 완료되었을 때 호출될 함수이다.
    해당 함수에 img태그의 src속성에 e.target.result를 부여하면, 이미지가 띄워진다.
<form action="<%=contextPath%>/insert.th" id="enroll-form" method="post" enctype="multipart/form-data">
제목 : <input type="text" name="title" required></td>
내용 : 
<textarea name="content" rows="5" style="resize:none;" required>
</textarea>
대표이미지 :
<img id="titleImg" width="250" height="170" onclick="clickFile(1);">
상세이미지 :
<img id="contentImg1" width="150" height="120" onclick="clickFile(2);">
<img id="contentImg2" width="150" height="120" onclick="clickFile(3);">
<img id="contentImg3" width="150" height="120" onclick="clickFile(4);"></td>

<div id="file-area" style="display:none">
    <input type="file" name="file1" onchange="loadImg(this, 1);" required>
    <input type="file" name="file2" onchange="loadImg(this, 2);" >
    <input type="file" name="file3" onchange="loadImg(this, 3);" >
    <input type="file" name="file4" onchange="loadImg(this, 4);" >
</div>


<script>
    //clickFile(num) 함수 : 클릭이 된 영역에 대한 정보를 매개변수로 받으며,
    //  해당 영역에 해당하는 input type=file에 클릭 이벤트를 준다.
    function clickFile(num) {
        $('input[name=file'+num+']').click();
    }
    
    //loadImg(this, num) 함수 : 파일을 첨부 혹은 취소하였을때 
    //  해당하는 img 태그에 이미지를 표현하거나 없앤다.
    function loadImg(inputFile, num){
        // inputFile : 현재 변화가 생긴 input type="file" 요소객체
        // num : 몇번째 input요소에 변화가 생겼는지 구분하기 위한 숫자
        //       해당되는 이미지 영역을 찾기위해 사용
        
        // input객체.files 는 배열로 반환되는 속성으로, 선택된 파일이 있다면
        // 1)inputFile.files[0] 에 선택된 파일에 대한 정보 담기면서 배열길이가 1이된다.
        // 2)취소하면 length가 0이 됨
        if(inputFile.files.length == 1) { 
            //파일이 선택된 경우로 미리보기 효과를 만들자.
            
            //파일을 읽어들일 FileReader 객체 생성
            const reader = new FileReader();
            
            //파일 읽어들이는 메소드 실행
            //아래 코드가 실행되는 순간 내부적으로 파일만의 고유한 url 부여
            reader.readAsDataURL(inputFile.files[0]);

            //파일 읽어들이기가 완료됐을 때 미리보기가 뜨도록 함수 정의
            reader.onload = function(e){
                //e.target.result = 읽어들인 파일의 고유한 url
                switch(num) {
                    case 1 : $('#titleImg').attr("src", e.target.result); break;
                    case 2 : $('#contentImg1').attr("src", e.target.result); break;
                    case 3 : $('#contentImg2').attr("src", e.target.result); break;
                    case 4 : $('#contentImg3').attr("src", e.target.result); break;
                }
            }
        } else { 
            //파일이 취소된 경우로 미리보기 됐던게 사라지게끔 하자
            switch(num) {
                case 1 : $('#titleImg').attr("src", null); break;
                case 2 : $('#contentImg1').attr("src", null); break;
                case 3 : $('#contentImg2').attr("src", null); break;
                case 4 : $('#contentImg3').attr("src", null); break;
            }
        }
    }
</script>

 

 

STEP2) board-mapper.xml (수정)

  • 사진게시글 작성 요청 시 실행될 sql문을 짠다.
-- BOARD INSERT 먼저 진행한다.
INSERT 
  INTO BOARD
  (
    BOARD_NO
  , BOARD_TYPE
  , BOARD_TITLE
  , BOARD_CONTENT
  , BOARD_WRITER
  )
  VALUES
  (
    SEQ_BNO.NEXTVAL
  , 2
  , 사용자가 입력한 제목
  , 사용자가 입력한 내용
  , 로그인한 회원의 번호
  )
  
--ATTACHMENT INSERT하기, 첨부파일 갯수만큼 돌린다.
INSERT
  INTO ATTACHMENT
  (
    FILE_NO
  , REF_BNO
  , ORIGIN_NAME
  , CHANGE_NAME
  , FILE_PATH
  , FILE_LEVEL
  )
  VALUES
  (
    SEQ_FNO.NEXTVAL
  , SEQ_BNO.CURRVAL
  , 첨부파일의원본명
  , 첨부파일의 실제 업로드명
  , 저장경로
  , 대표이미지면1 / 상세이미지면2
  )

 

 

STEP3) ThumbnailInsertController.java

  • 작성위치 : com.br.board.controller
    서블릿/매핑값 insert.th
  • 사진게시글에서 작성을 눌렀을때 요청되어 db에 데이터를 기록하는 서블릿
  • enctype="~"로 넘겨받았을 경우 multipartRequest 로 변경해야만 값을 뽑을 수 있
  • 사진게시글용 폴더 만들기 thumbnail_upfiles (webProject/src/main/webapp/resources)
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    request.setCharacterEncoding("UTF-8");
    if(ServletFileUpload.isMultipartContent(request)) {
        int maxSize = 10*1024*1024;
        //request.getSession().getServletContext() = 웹 어플리케이션 객체 (전역객체)
        //.getRealPath("/~~")에서 / == webapp이므로 이후 경로 기재
        String savePath = request.getSession().getServletContext().getRealPath("/resources/thumbnail_upfiles/");

        MultipartRequest multiRequest = new MultipartRequest(request, savePath, maxSize, "UTF-8", new MyFileRenamePolicy());
        //업로드완
			
        //DB에 기록하기 위해 데이터 담기
        HttpSession session = request.getSession();
        int userNo = ((Member)session.getAttribute("loginUser")).getUserNo();
        Board b = new Board();
        b.setBoardTitle(multiRequest.getParameter("title"));
        b.setBoardContent(multiRequest.getParameter("content"));
        b.setBoardWriter(String.valueOf(userNo));
			
        // Attachment테이블에 insert할 데이터 담기 => 여러개일수 있으므로 ArrayList
        ArrayList<Attachment> list = new ArrayList<>();
        for(int i=1; i<=4; i++) {
            String key = "file"+i; // 해당 키값을 가진 input을 하나씩 확인한다.
            if(multiRequest.getOriginalFileName(key) != null) {
                //첨부파일이 있을 경우
                Attachment at = new Attachment();
                at.setOriginName(multiRequest.getOriginalFileName(key));
                at.setChangeName(multiRequest.getFilesystemName(key));
                at.setFilePath("resources/thumbnail_upfiles/");
                //대표이미지면 1, 아니면 2 입력
                at.setFileLevel(i==1? 1 : 2);
                list.add(at);
            }
        }
        
        //요청처리 하고오기
        int result = new BoardService().insertThumbnailBoard(b, list);
			
        if(result>0) {
            //성공 => 목록페이지 이미 포워딩하는 url 존재 => url재요청진행
            session.setAttribute("alertMsg", "성공적으로 게시글이 등록되었습니다");
            response.sendRedirect(request.getContextPath()+"/list.th");
        }else {
            //에러페이지
        }
    }
}

 

 

STEP4) BoardService.java (수정)

  • insertThumbnailBoard(b, list) 메소드 작성
public int insertThumbnailBoard(Board b, ArrayList<Attachment> list) {
    Connection conn = getConnection();
    //Board 테이블 insert
    int result1 = new BoardDao().insertThBoard(conn,b);
		
    //Attachment테이블 insert
    int result2 = new BoardDao().insertAttachmentList(conn, list); 
		
    if(result1*result2>0) {
        commit(conn);
    }else {
        rollback(conn);
    }
    return result1*result2;
}

 

 

STEP5) BoardDao.java (수정)

  • insertThBoard(conn,b) 메소드 작성
  • insertAttachmentList(conn, list) 메소드 작성
public int insertThBoard(Connection conn,Board b) {
    int result = 0;
    PreparedStatement pstmt = null;
    String sql = prop.getProperty("insertThBoard");
    try {
        pstmt = conn.prepareStatement(sql);
        pstmt.setString(1, b.getBoardTitle());
        pstmt.setString(2, b.getBoardContent());
        pstmt.setString(3, b.getBoardWriter());
        result = pstmt.executeUpdate();
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        close(pstmt);
    }
    return result;
}

public int insertAttachmentList(Connection conn, ArrayList<Attachment> list) {
    int result = 0;
    PreparedStatement pstmt = null;
    String sql = prop.getProperty("insertAttachmentList");
    try {
        //list의 갯수만큼 반복문으로돌려서 insert하기
        for(Attachment at : list) {
            pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, at.getOriginName());
            pstmt.setString(2, at.getChangeName());
            pstmt.setString(3, at.getFilePath());
            pstmt.setInt(4, at.getFileLevel());
            result = pstmt.executeUpdate();
        }
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        close(pstmt);
    }
    return result;
}

 

 

 


썸네일 목록페이지

일반 목록과 다르게 첨부된 사진으로 썸네일 목록을 만들어보자

페이징 처리는 생략한다.


 

 

STEP1) thumbnailListView.jsp

  • 생성위치 : webProject/src/main/webapp/views/board
  • 썸네일이 보이는 표현하는 사진게시판목록을 구성한다.
<style>
.....
.thumbnail {
    border: 1px solid white;
    width: 220px;
    /*옆으로 배치하기 위해 인라인, 가로세로 지정하기위해 블럭*/
    display: inline-block;
    margin:14px;
}
</style>
.....        
        <div class="thumbnail" align="center">
        <!-- 사용자가 입력한 이미지는 크기가 제각각일것 > 이미지 크기 고정-->
        <img src="" width="200" height="150">
            <p>
                No.23 제목자리 <br>
                조회수 : 120
            </p>
        </div>
        <div class="thumbnail" align="center">
            <img src="" width="200" height="150">
            <p>
                No.23 제목자리 <br>
                조회수 : 120
            </p>
        </div>
......

 

 

STEP2) board-mapper.xml (수정)

  • 목록을 표현하는데 필요한 데이터들을 sql문을 작성하며정리해본다.
  • file_path와 change_name을 연이어서 하나의 문자열로 가져온다
    이때 별칭 필수 (데이터 꺼낼때 사용할 명칭)
SELECT 
       BOARD_NO
     , BOARD_TITLE
     , COUNT
     , FILE_PATH || CHANGE_NAME "TITLEIMG"  
  FROM BOARD B
  JOIN ATTACHMENT ON (BOARD_NO = REF_BNO)
 WHERE BOARD_TYPE = 2
   AND B.STATUS = 'Y'
   AND FILE_LEVEL = 1
 ORDER
    BY BOARD_NO DESC;

 

 

STEP3) Board.java (수정)

  • 상단의 쿼리문 확인시, Board에 필요한 데이터를 다 담을 수 없음(TITLEIMG )
    >> 필드를 하나 추가해준다.
  • 이처럼 필드는 얼마든지 추가해 줄 수 있다.
    기존의 필드를 지우는것과 달리 문제가 안됨. getter, setter 메소드만 추가로 잘 생성해준다.
private String titleImg; // 대표이미지의경로

 

 

STEP4) menubar.jsp (수정)

  • 메뉴바의 사진게시판 버튼을 클릭하면 이동하도록 매핑값을 입력한다.
<a href="<%=contextPath%>/list.th">사진게시판</a></div>

 

 

STEP5) ThumbnailListController.java

  • 생성위치 : com.br.board.controller
    서블릿/매핑값 list.th
  • 사진게시판 목록으로 포워딩하는 서블릿이다.
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //thumbnailListView.jsp 상에 필요한 데이터 조회해서 가야됨
    ArrayList<Board> list = new BoardService().selectThumbnailList();
    request.setAttribute("list", list);
    //응답페이지
    request.getRequestDispatcher("views/board/thumbnailListView.jsp").forward(request, response);
}

 

 

STEP6) BoardService.java (수정)

  • selectThumbnailList() 메소드 생성
public ArrayList<Board> selectThumbnailList(){
    Connection conn = getConnection();
    ArrayList<Board> list = new BoardDao().selectThumbnailList(conn);
    close(conn);
    return list;
}

 

 

STEP7) BoardDao.java (수정)

  • selectThumbnailList(conn) 메소드 생성
public ArrayList<Board> selectThumbnailList(Connection conn) {
    ArrayList<Board> list = new ArrayList<>();
    PreparedStatement pstmt = null;
    ResultSet rset = null;
    String sql = prop.getProperty("selectThumbnailList");
    try {
        pstmt = conn.prepareStatement(sql);
        rset = pstmt.executeQuery();
        while(rset.next()) {
            Board b = new Board();
            b.setBoardNo(rset.getInt("board_no"));
            b.setBoardTitle(rset.getString("board_title"));
            b.setCount(rset.getInt("count"));
            b.setTitleImg(rset.getString("titleimg"));
            list.add(b);
        }
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        close(rset);
        close(pstmt);
    }
    return list;
}

 

 

STEP8) thumbnailListView.jsp (수정)

  • 상단에 전달받은 데이터를 뽑아놓는다
  • list에 담긴 Board 갯수만큼 썸네일 div 뿌린다.
<%@ page import="com.br.board.model.vo.Board, java.util.ArrayList" %>
<%
    ArrayList<Board> list = (ArrayList<Board>)request.getAttribute("list");
%>
.....
    <% for(Board b : list) { %>
        <div class="thumbnail" align="center">
            <img src="<%=contextPath%>/<%=b.getTitleImg()%>" width="200" height="150">
            <p>
                No.<%=b.getBoardNo()%> <%=b.getBoardTitle()%><br>
                조회수 : <%=b.getCount()%>
            </p>
        </div>
    <% } %>   
.....

 

 


글작성 페이지 가기

목록에서 글작성 버튼을 누르면

사진게시글을 작성할수 있는 페이지로 이동한다


STEP1) thumbnailListView.jsp(수정) 

  • 글작성 버튼에 enrollForm.th 서블릿을 연결한다.
<a href="<%=contextPath%>/enrollForm.th">글작성</a>

 

 

STEP2) ThumbnailEnrollFormController.java

  • 생성위치 : com.br.board.controller
    서블릿/매핑값 enrollForm.th
  • 글작성 페이지로 포워딩한다.
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //조회해갈 데이터 없이 포워딩만 해가면 됨.
    request.getRequestDispatcher("views/board/thumbnailEnrollForm.jsp").forward(request, response);
}

 

 

 

 

 

 

댓글