국비 73 - 게시글 수정

냐아암·2023년 8월 9일
0

국비

목록 보기
94/114

게시글 수정

📍 이미지 수정/삭제 여부 신경쓰기

<button id="updateBtn" onclick="location.href='write?mode=update&type=${param.type}&cp=${cp}&no=${param.no}'">수정</button>

Servlet

@WebServlet("/board/write")
public class BoardWriteController extends HttpServlet {

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		
		
		try {
			
			String mode = req.getParameter("mode"); // insert / update 구분
			
			System.out.println(mode);
			
			// insert는 별도 처리 없이 jsp로 위임
			
			// update는 기존 게시글 내용을 조회하는 처리가 필요
			if(mode.equals("update")) {
				
				int boardNo = Integer.parseInt(req.getParameter("no")); 
				
				// 게시글 상세조회 서비스를 이용해서 기존 내용 조회
				// ( new BoardService() : 객체를 생성해서 변수에 담지 않고 바로 사용 -> 1회성 사용 ) 
				BoardDetail detail = new BoardService().selectBoardDetail(boardNo);
				
				// 개행 문자 처리 해제(<br> -> \n)
				detail.setBoardContent(detail.getBoardContent().replaceAll("<br>", "\n"));
				
				req.setAttribute("detail", detail); // jsp에서 사용할 수 있도록 request에 값을 세팅
				
			}
			
			String path = "/WEB-INF/views/board/boardWriteForm.jsp";
			req.getRequestDispatcher(path).forward(req, resp);
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		
	}

<form action="write" enctype="multipart/form-data" method="post" class="board-write"
            onsubmit="return writeValidate()">
<input type="hidden" name="deleteList" id="deleteList" value="">
// 미리보기 관련 요소 모두 얻어오기

const inputImage = document.getElementsByClassName("inputImage");
const preview = document.getElementsByClassName("preview");
const deleteImage = document.getElementsByClassName("delete-image");

// 게시글 수정 시 삭제된 이미지의 레벨(위치)를 저장할 input 요소
const deleteList = document.getElementById("deleteList");

// 게시글 수정 시 삭제된 이미지의 레벨(위치)를 기록해 둘 Set 생성
const deleteSet = new Set(); // 순서 X, 중복 허용 X (여러 번 클릭 시 중복 값 저장을 방지)

for(let i=0; i<inputImage.length; i++){

    // 파일이 선택되었을 때
    inputImage[i].addEventListener("change", function(){

        if(this.files[0] != undefined) { // 파일이 선택되었을 때
            const reader = new FileReader(); // 선택된 파일을 읽을 객체 생성
            reader.readAsDataURL(this.files[0]);
            // 지정된 파일을 읽음 -> result에 저장(URL 포함) -> URL을 이용해서 이미지를 볼 수 있다.

            reader.onload = function(e){ // reader가 파일을 다 읽어온 경우
                // e.target == reader
                // e.target.result == 읽어들인 이미지의 URL
                // preview[i] == 파일이 선택된 input 태그와 인접한 preview 이미지 태그
                preview[i].setAttribute("src", e.target.result);

                // 이미지가 성공적으로 불러와졌을 때
                // deleteSet에서 해당 레벨 제거(삭제 목록)
                deleteSet.delete(i);
            }
        } else { // 파일이 선택되지 않았을 때 == 취소 눌렀을 때
            preview[i].removeAttribute("src"); // src 속성 제거
        }
    });

    // 미리보기 삭제 버튼(x)이 클릭되었을 때 동작
    deleteImage[i].addEventListener("click", function(){

        // 미리보기가 존재하는 경우에만 (이전에 추가된 이미지가 있을 때만) X 버튼 동작
        if(preview[i].getAttribute("src") != ""){

            // 미리보기 삭제
            preview[i].removeAttribute("src");
    
            // input의 값을 "" 만들기
            inputImage[i].value="";
    
            // deleteSet에 삭제된 이미지 레벨(i) 추가
            deleteSet.add(i);
        }

        
    })

}

// 게시글 작성 유효성 검사
function writeValidate(){
    const boardTitle = document.getElementsByName("board-title")[0];
    const boardContent = document.querySelector("[name='boardContent']");

    if(boardTitle.value.trim().length == 0){
        alert("제목을 입력해주세요!!!");
        boardTitle.value = "";
        boardTitle.focus();
        return false;
    }

    if(boardContent.value.trim().length == 0){
        alert("내용을 입력해주세요!!!");
        boardContent.value = "";
        boardContent.focus();
        return false;
    }

    // 제목, 내용이 유효한 경우
    // deleteList(input 태그)에 deleteSet(삭제된 이미지 레벨)을 추가
    // -> JS 배열 특징 사용
    // --> JS 배열을 HTML 요소 또는 console에서 출력하게 되는 경우 1,2,3 같은 문자열로 출력됨
    //     (배열 기호가 벗겨짐)

    // * Set -> Array로 변경 -> deleteList.value에 대입

    // Array.from(유사배열 | 컬렉션) : 유사배열 | 컬렉션을 배열로 변환해서 반환
    deleteList.value = Array.from( deleteSet );

    return true;
}

Servlet

@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		
		try {
			
			// insert/update 구분 없이 전달받은 파라미터 모두 꺼내서 정리하기
			
			// *** encrypt="multipart/form-data" 인코딩 미지정 형식의 요청 ***
			// -> HttpServletRequest로 파라미터 얻어오기 불가능!
			// --> MultipartRequest를 이용(cos.jar 라이브러리 제공)
			// ---> 업로드 최대 용량, 저장 실제 경로, 파일명 변경 정책, 문자 파라미터 인코딩 설정
			
			int maxSize = 1024*1024*100; // 업로드 최대 용량(100MB)
			
			HttpSession session = req.getSession(); // session 얻어오는 것은 지장 없음(사용 가능)
			
			String root = session.getServletContext().getRealPath("/"); // webapp까지의 경로
			
			String folderPath = "/resources/images/board/"; // 파일 저장 폴더 경로
			
			String filePath = root + folderPath;
			
			String  encoding = "UTF-8"; // 파라미터 중 파일 제외 파라미터(문자열)의 인코딩
			
			// ** MultipartRequest 객체 생성 **
			// -> 객체가 생성됨과 동시에 파라미터로 전달된 파일이 지정된 경로에 저장(업로드)된다.
			MultipartRequest mpReq = new MultipartRequest(req, filePath, maxSize, encoding, new MyRenamePolicy());
			
			// MultipartRequest.getFileNames()
			// - 요청 파라미터 중 모든 file 타입 태그의 name 속성 값을 얻어와
			// 	 Enumeration 형태로 반환 (Iterator의 과거 버전)
			// 	 --> 해당 객체에 여러 값이 담겨 있고 이를 순서대로 얻어오는 방법을 제공
			//		(보통 순서가 없는 모음(Set과 같은 경우)에서 하나씩 꺼낼 때 사용)
			
			Enumeration<String> files = mpReq.getFileNames();
			// file 타입 태그의 name 속성 0,1,2,3,4가 순서가 섞인 상태로 얻어와짐
			
			// * 업로드된 이미지의 정보를 모아둘 List 생성
			List<BoardImage> imageList = new ArrayList<BoardImage>();
			
			while(files.hasMoreElements()) { // 다음 요소가 있으면 true
				String name = files.nextElement(); // 다음 요소(name 속성 값)를 얻어옴
				
				// System.out.println("name : " + name);
				// file 타입 태그의 name 속성 값이 얻어와 짐
				// + 업로드가 안 된 file 타입 태그의 name 도 얻어와 짐
				
				String rename = mpReq.getFilesystemName(name); // 변경된 파일명
				String original = mpReq.getOriginalFileName(name); // 원본 파일명
				
				// System.out.println("rename: "+ rename);
				// System.out.println("original: "+ original);
				
				if(original != null) { // 업로드된 파일이 있을 경우
									   // == 현재 files에서 얻어온 name 속성을 이용해서
									   //	 원본 또는 변경을 얻어왔을 때 그 값이 null이 아닌 경우
					
					// 이미지 정보를 담은 객체(BoardImage)를 생성
					BoardImage image = new BoardImage();
					
					image.setImageOriginal(original); // 원본명
					image.setImageReName(folderPath + rename); // 폴더 경로 + 변경명
					image.setImageLevel(Integer.parseInt(name)); // 이미지 위치
					
					imageList.add(image); // 리스트에 추가
					
				} // if문 끝
				
			} // while문 끝
			
			// * 이미지를 제외한 게시글 관련 정보 *
			String boardTitle = mpReq.getParameter("board-title");
			String boardContent = mpReq.getParameter("boardContent");
			int boardCode = Integer.parseInt(mpReq.getParameter("type")); // hidden
			
			Member loginMember = (Member)session.getAttribute("loginMember");
			int memberNo = loginMember.getMemberNo(); // 회원 번호
			
			// 게시글 관련 정보를 하나의 객체(BoardDetail)에 담기
			BoardDetail detail = new BoardDetail();
			
			detail.setBoardTitle(boardTitle);
			detail.setBoardContent(boardContent);
			detail.setMemberNo(memberNo);
			// boardCode는 별도 매개변수로 전달 예정
			
			// ------------- 게시글 작성에 필요한 기본 파라미터 얻어오기 끝 -------------
			BoardService service = new BoardService();
			
			// 모드(insert/update)에 따라서 추가 파라미터 얻어오기 및 서비스 호출
			String mode = mpReq.getParameter("mode"); // hidden
			
			if(mode.equals("insert")) { // 유지 보수를 위해서 else 말고 따로 조건 기재
				
				// 게시글 삽입 서비스 호출 후 결과 반환 받기
				// -> 반환된 게시글 번호를 이용해서 상세조회로 리다이렉트 예정
				
				int boardNo = service.insertBoard(detail, imageList, boardCode);
				
				String path=null;
				
				if(boardNo>0) { // 성공
					session.setAttribute("message", "게시글이 등록되었습니다.");
					path = "detail?no=" + boardNo + "&type=" + boardCode;
				} else { // 실패
					session.setAttribute("message", "게시글 등록 실패");
					path = "write?mode=" + mode + "&type="+boardCode;
				}
				
				resp.sendRedirect(path); // 리다이렉트
			}
			
			if(mode.equals("update")) { // 수정
				
				// 앞선 코드는 동일(업로드된 이미지 저장, imageList 생성, 제목/내용 파라미터 동일)
				
				// + update일 때 추가된 내용
				// 어떤 게시글 수정? -> 파라미터 no
				// 나중에 목록으로 버튼 만들 때 사용할 현재 페이지 -> 파라미터 cp
				// 이미지 중 x버튼을 눌러서 삭제할 이미지 레벨 목록 -> 파라미터 deleteList
				int boardNo = Integer.parseInt(mpReq.getParameter("no"));
				int cp = Integer.parseInt(mpReq.getParameter("cp"));
				String deleteList = mpReq.getParameter("deleteList");
				
				// 게시글 수정 서비스 호출 후 결과 반환 받기
				// imageList, detail, boardNo, deleteList
				detail.setBoardNo(boardNo);
				
				// detail, imageList, deleteList
				int result = service.updateBoard(detail, imageList, deleteList);
				
				String path = null;
				String message = null;
				
				if(result>0) { // 성공
					
					// detail?no=1000?type=1&cp=2
					path = "detail?no=" + boardNo + "&type=" + boardCode + "&cp=" + cp;
					// 상세조회 페이지 요청 주소
					
					message = "게시글이 수정되었습니다.";
					
				} else { // 실패
					
					// 수정 화면으로 이동
					
					// 상세조회 -> 수정화면 -> 수정 -> (성공)상세조회
					//						   -> (실패)수정화면
					
					// write?mode=update&type=2&cp=1&no=1525
					path = req.getHeader("referer");
					// referer : HTTP 요청 흔적(요청 바로 이전 페이지 주소)
					
					message = "게시글 수정 실패";
					
				}
				
				session.setAttribute("message", message);
				resp.sendRedirect(path);
				
			}
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}
    

Service

/** 게시글 수정 service
	 * @param detail
	 * @param imageList
	 * @param deleteList
	 * @return result
	 * @throws Exception
	 */
	public int updateBoard(BoardDetail detail, List<BoardImage> imageList, String deleteList) throws Exception {
		
		Connection conn = getConnection();
		
		// 1. 게시글 부분(제목, 내용, 마지막 수정일) 수정
		// 1) XSS 방지 처리(제목, 내용)
		detail.setBoardTitle(Util.XSSHandling(detail.getBoardTitle()));
		detail.setBoardContent(Util.XSSHandling(detail.getBoardContent()));
		
		// 2) 개행 문자 처리(내용)
		detail.setBoardContent(Util.newLineHandling(detail.getBoardContent()));
		
		// 3) DAO 호출
		int result = dao.updateBoard(conn, detail);
		
		if(result>0) { // 게시글 수정 성공 시 
			
			// 2. 이미지 부분 수정(기존 -> 변경, 없다가 -> 추가)
			for(BoardImage img : imageList) {
				
				img.setBoardNo(detail.getBoardNo()); // 게시글 번호 세팅
				
				// 이미지 1개씩 수정
				result = dao.updateBoardImage(conn, img);
				// result == 1 : 수정 성공
				
				// result == 0 : 수정 실패 -> 기존에 없다가 새로 추가된 이미지
				// 						 -> insert 진행
				
				if(result == 0) {
					result = dao.insertBoardImage(conn, img);
				}
				
				// 나를 위한 주석
				// for문으로 이미지 하나씩 접근하는 거니까 !!! update가 안 되면 -> insert 해야지
				// 그것도 안 되면 sql 실패
				
			} // 향상된 for문 끝
			
			// 3. 이미지 삭제
			// deleteList( "1,2,3" 이런 모양, 없으면 ""(빈 문자열) )
			if(!deleteList.equals("")) { // 삭제된 이미지 레벨이 기록되어 있을 때만 삭제
				result = dao.deleteBoardImage(conn, deleteList, detail.getBoardNo());
			}
			
		} // 게시글 수정 성공 시 if 문 끝
		
		if(result > 0)	commit(conn);
		else			rollback(conn);
				
		close(conn);
		
		return result;
	}

DAO

/** 게시글 부분 수정 DAO
	 * @param conn
	 * @param detail
	 * @return result
	 * @throws Exception
	 */
	public int updateBoard(Connection conn, BoardDetail detail) throws Exception {
		
		int result = 0;
		
		try {
			
			String sql = prop.getProperty("updateBoard");
			
			pstmt = conn.prepareStatement(sql);
			
			pstmt.setString(1, detail.getBoardTitle());
			pstmt.setString(2, detail.getBoardContent());
			pstmt.setInt(3, detail.getBoardNo());
			
			result = pstmt.executeUpdate();
			
		} finally {
			
			close(pstmt);
			
		}
		
		return result;
	}

sql

<!-- 게시글 부분 수정 -->
	<entry key="updateBoard">
		UPDATE BOARD
		SET BOARD_TITLE = ?, BOARD_CONTENT=?, UPDATE_DT= SYSDATE
		WHERE BOARD_NO = ?
	</entry>

DAO

/**  게시글 이미지 수정 DAO
	 * @param conn
	 * @param img
	 * @return result
	 * @throws Exception
	 */
	public int updateBoardImage(Connection conn, BoardImage img) throws Exception{
		
		int result = 0;
		
		try {
			
			String sql = prop.getProperty("updateBoardImage");
			
			pstmt = conn.prepareStatement(sql);
			
			pstmt.setString(1, img.getImageReName());
			pstmt.setString(2, img.getImageOriginal());
			pstmt.setInt(3, img.getBoardNo());
			pstmt.setInt(4, img.getImageLevel());
			
			result = pstmt.executeUpdate();
			
		} finally {
			close(pstmt);
		}
		
		return result;
	}
    

sql

<!-- 게시글 이미지 수정 -->
	<entry key="updateBoardImage">
		UPDATE BOARD_IMG
		SET IMG_RENAME = ?, IMG_ORIGINAL=?
		WHERE BOARD_NO=?
		AND IMG_LEVEL=?
	</entry>

DAO

/** 이미지 삭제 DAO
	 * @param conn
	 * @param deleteList
	 * @param boardNo
	 * @return result
	 * @throws Exception
	 */
	public int deleteBoardImage(Connection conn, String deleteList, int boardNo) throws Exception {
		
		int result = 0;
		
		try {
			// 							완성되지 않은 SQL
			String sql = prop.getProperty("deleteBoardImage")+ deleteList + ")";
			// "DELETE FROM BOARD_IMG WHERE BOARD_NO = ? AND IMG_LEVEL IN (
			
			pstmt = conn.prepareStatement(sql);
			
			pstmt.setInt(1, boardNo);
			
			result = pstmt.executeUpdate();
			
		} finally {
			close(pstmt);
		}
		
		return result;
	}
    
<!-- 게시글 이미지 삭제 -->
	<entry key="deleteBoardImage">
		DELETE FROM BOARD_IMG
		WHERE BOARD_NO = ?
		AND IMG_LEVEL IN (
	</entry>
profile
개발 일지

0개의 댓글