게시글 작성 구현 (1)

DeadWhale·2022년 6월 2일
0

Servlet/JSP

목록 보기
19/22
post-thumbnail

Community 웹 사이트 프로젝트 진행중 게시글 작성부분 수업 내용을 작성한 글

기억할것

  • e.printStackTrace( ) 까먹지 말자
  • html에서 X(가로) Y(세로) Z(대각선)
    • Z-INDEX : 10; 화면에 보이는 우선순위 지정 (높을수록 우선순위⬆️
  • 여러 요소에 이벤트를 반복하며 추가할때 i 와 0을 잘 생각해서 쓰자
  • MultipartRequest 객체 요소는 cos라이브러리에서 제공하는거다.

HTML/CSS

boardWriterForm 게시글 작성 형식의 html 작성

이런 느낌으로 구현
input type = "file"으로 입력시 js를 통해 미리보기를 구현한다.
thumbnail 영역의 x 버튼은 폰트어썸으로 가져온거다 예쁘다.


각각의 input image들의 name은 각각의 이미지 위치를 의미하는 레벨을 Servlet으로 전달한다.

 <!-- 1번 사진 ( 썸네일은 0번 )-->
<div class="boardImg">
	<label for="img1">
		<img src="" class="preview"> 
	</label> 
	<input type="file" class="inputImage" id="img1" name="1" accept="image/*">
	<span class="delete-image ">&times;</span>
</div>

이건 서블릿으로 전달할때 새로작성하는건지 수정하는건지 체크하는 변수
+ 게시판의 종류를 전달한다. 전달은 보내지만 사용자에게는 보낼 필요가 없어서 hidden으로 전달한다.

 <!-- 숨겨진 값(hidden) -->
            <!-- 동작을 구분 (수정/새로 작성같은) -->
            <input type="hidden" name="mode" value="insert">
            <!-- 어디 게시판인지 구분. -->
            <input type="hidden" name="type" value="1">

CSS

/* 게시글 제목 input에 focus 된 순간 */
/*  :focus-within == 자식 부분에 focus 되었을때 */
.board-title:focus-within{    border-bottom-color: #455ba8; }
<h1 class="board-title">
<input type="text" name="boardTitle" placeholder="제목을 입력해주세요." autocomplete="off">
</h1>


JS

js에서는 미리보기 이미지를 넣어주고
제목과 내용이 없을경우 submit의 기능을 제한하는 함수를 작성해야한다.
모든 이미지 영역에는 preview / inputImage / delete-image 3개의 클래스가 있는데 이걸 이용하면 편하게 된다.

/* 미리보기 관련 요소들 전부 얻어오기 */
// --> 동일한 개수의 요소가 존재함 == 인덱스가 일치함
const inputImage = document.getElementsByClassName("inputImage");
const preview = document.getElementsByClassName("preview");
const deleteImage = document.getElementsByClassName("delete-image");

위에서 각각의 변수들은 배열형태이니 반복문을 이용해 각각의 요소에 이벤트를 추가해준다.

미리보기 함수

for(let i =0; i<inputImage.length; i++){
    /* 인풋 이미지 i번째 요소가 변했을 때. */
    /* ==  파일이 선택 되었을 때 동작*/
    inputImage[i].addEventListener("change",function(){
        if(this.files[0] != undefined){//파일이 선택된 경우
            const reader = new FileReader(); //선택된 파일을 읽을 객체를 생성
            reader.readAsDataURL(this.files[0]); //reader에 result(url 포함)에 저장됨 url을 이용해 이미지 확인 가능
            reader.onload=function(e){
                //e.targer = reader
                //e.target.result == 읽어들인 이미지의 URL
                //preview[i] == 파일이 선택된 input 태그와 인접한 preview 이미지 태그.
                preview[i].setAttribute("src",e.target.result);
            }
        }else{//파일이 선택되지 않았을 때.
            preview[i].removeAttribute("src");
        }
    });
    /* 미리보기 삭제 버튼이 클릭 되었을 때의 동작 */
    deleteImage[i].addEventListener("click",function(){
        //미리보기 삭제.
        preview[i].removeAttribute("src");
        //input 값 빈칸으로 지우기
        inputImage[i].value="";
    })
}

제목과 내용의 입력값을 확인하는 함수.

function writeValidate(){
  -- 제목과 내용 모두 name속성값으로 가져오면 배열형태인대 하나뿐이라 0번째를 가져온다.
    const boardTitle = document.getElementsByName("boardTitle")[0];
    const boardContent = document.getElementsByName("boardContent")[0];
	-- 제목 확인
    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
    };
}

Servlet (Front-Controll)

front controll 방식 /write/* 으로 들어오는 모든 요청을 캐치한다.

doGet 요청

boardList에서 작성하기 혹은 수정하기 버튼을 눌르면 오는 요청
/board/write 으로 Get 방식 요청이 들어오면 처리한다
이때 update인지 insert인지는 요청받을때 전달되는 mode parameter로 결정된다
mode.equals("update") 일 경우에는 고려해야할 부분이 많지만
insert의 경우 단순히 새로운 JSP창을 만드는 거라 크게 고려할 부분이 없다.

String path = "/WEB-INF/views/board/boardWriteForm.jsp";
req.getRequestDispatcher(path).forward(req, resp);

doPost 요청

하 진짜 어려웠다 단순 작성인데 이렇게 고려할 부분이 많았다니..
일단 이 파트중 가장 기억나는 부분은 try-catch에서 e.printStackTrace를 까먹지말고 적자이다
난 몰랐는데 내가 지금 까지 꼴에 버그 잘 알아낸다고 생각한건 다
e.printStackTrace( )덕분이였다 에러 코드가 안뜨니 진짜 간단한 코드 에러였는데 에러가 안뜨니깐 정신이 혼미했다.

INSERT / UPDATE 둘 모두 공통적으로 사용되는 파라미터들을 꺼내 정돈하는 과정부터 수행했다.

Form으로 전달받을떄 인코딩되지 않은 date그 자체로 넘어오는데
이를 인코딩할건하고 이미지같은건 서버에 업로드하는 MultipartRequest객체 준비부터 시작했다

준비할건 총 4개의 설정이 필요했다.(정보가 담긴 req는 제외)

업로드 최대 용량 ,
저장 실제 경로 ,
문자열파라미터 인코딩 ,
파일명 변경 정책 ,

1. 최대 업로드 용량 정하기

int maxSize = 1024*1024*100; //100MB

2. 저장할 실제경로 추출+조합

//저장할 실제 경로
HttpSession session = req.getSession();
// 최상위 경로 ( " / "  == webapp 폴더 )의 컴퓨터 상의 실제 절대 경로를 얻어옴.
String root = session.getServletContext().getRealPath("/"); //webapp폴더까지의 경로
//실제 파일이 저장되는 폴더의 경로	
String folderPath = "/resources/images/board/"; //저장될 폴더 경로 webapp 아래부터
// 위에 두개 합쳐서 하나의 경로로 만들기
// memberProfile 폴더까지의 절대 경로
String filePath = root+folderPath;

3. 이름을 변경하기 위한 함수

이건 따로 public Static으로 작성되어있는
MyRenamePolicy() 함수를 이용

4.파일외 문자열들의 인코딩 형식 작성

String encoding = "UTF-8"; //파라미터중 파일 외 파라미터(문자열) 인코딩 지정

MulitPartRequest 객체 생성

MultipartRequest mpReq = new MultipartRequest(req, filePath, maxSize, encoding,new MyRenamePolicy());
									(전달받은 값 / 이미지저장경로/최대 용량 / 인코딩방식 / 이름 변경함수(이름이 변경되어 return))

이미지"들을" 가져오는 로직

이미지들은 0개이상 5개 이하로 전달될 수 있다
Enumeration를 이용해 파일들의 name 속성값을 가져와야한다.

Enumeration : (Iterator의 과거 버전)

  • 해당 객체에 여러 값이 담겨잇고 순서대로 얻어오는 방법을 제공한다
  • 보통은 순서가 없는 모둠(Set같은것들)에서 하나씩 꺼내는데 사용
Enumeration<String> files = mpReq.getFileNames(); 
//file 타입 태그의 name 속성 0 , 1 , 2, , 3 , 4가 순서가 섞인 상태로 저장됨

처음에 헷갈린게 어떻게 사진의 name만 뽑아내는거지? 였는데
생각해보니 input type = "file"으로 선별이되는거엿다.
전달할 때 부터 이건 파일이라고 전달하는거다

이렇게 이미지들의 name속성(Image-Level)을 추출한 후 imageList객체에 저장한다.

//				업로드된 이미지의 정보를 모아둘 List 생성
List<BoardImage> imageList = new ArrayList<BoardImage>();

while(files.hasMoreElements()) {//다음 요소가 있는가? 있으면true
String name = files.nextElement(); // 다음 (name 속성 값)를 얻어옴
//					System.out.println("name은 : "+name);

//files타입 태그들의 name속성값을 모두 얻어옴
//업로드가 안된 file타입 태그의 name오 얻어와짐
String rename =mpReq.getFilesystemName(name); //변경된 파일명
String original =mpReq.getOriginalFileName(name); //원본 파일명

//					System.out.println("reaname:"+rename);
//					System.out.println("original:"+original);

if(rename != null){ //업로드된 파일이 있는 경우						
				//현재 files에서 얻어온 name속성을 이용해
				//원복 또는 변경을 얻어왔을 때 그 값이 null이 아닌 경우

// 이미지정보를 담은 객체(BoardImage)를 생성
BoardImage image = new BoardImage();
image.setImageOriginal(original); //원본명(다운로드할때 사용)
image.setImageReName(folderPath+rename);  //저장되는 폴더 경로+ 파일명 (그냥 파일만 저장하면 안된다.)
image.setImageLevel(Integer.parseInt(name)); // 이미지 위치도 저장 (0은 썸네일)
imageList.add(image); //리스트에 추가
} //if문 끝
	
	
} //while 끝

위의 로직은 이해는 되는데 안보고 쓰라하면 어지러울것 같다
다시한번 복기가 필요하다.
핵심은 이미지요소를 반복하며 imageList제네릭 List에 저장하는 것이다.


이미지 저장을 제외하고는 크게 어려운 부분이 없었다
UTF-8으로 인코딩된 제목 / 내용 / 게시판 종류를 파라미터로 얻어오고
Session으로 로그인된 정보를 가져와 하나의 BoardDetail객체에 저장하는 과정이 필요하다.

	// ** 이미지를 제외한 게시글 관련 정보들 저장
// 그냥 req으로 하면null로 나옴
String boardTitle = mpReq.getParameter("boardTitle");
String boardContent = mpReq.getParameter("boardContent");
int boardCode = Integer.parseInt(mpReq.getParameter("type"));

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는 별도 매개변수로 전달
//만들면되지만 구지 안만들어도 괜찮아서

이 때!! 왜 이미지 리스트 게시판번호는 저장안하는지 궁금했는데
간단했다 "담을 필요가 없어서"이다 어차피 service에서 수행하는데
VO가 무거울 필요가 없기도 하고 Set을하고 Get으로 뽑는 코드가 더 길어서 그렇기도 하고...

원래 여기서 service수행 후 돌아온 값으로 redirect하지만

단순 Insert의 경우 밑에 수행이 더 복잡한게 크게 없어 코드만 넣고
Service -DAO - SQL은 다음글에 적어야겟다.

//---------------------------게시글 작성에 필요한 기본 파라미터 얻어오기 끝-------------------

BoardService service = new BoardService();


//모드 (insert / update) 에 따라서 추가 파라미터 얻어오기 및 서비스 호출
String mode = mpReq.getParameter("mode");

if(mode.equals("insert")){ //삽입
	//게시글 삽입 서비스 호출 후 결과(삽입된 게시글의 번호) 반환
	//반환된 게시글 번호를 이용해 상세조회 화면으로 redirect해 보여준다
	int boardNo = service.insertBoard(detail,imageList,boardCode);
	

	String path = null;
	
	if(boardNo>0) { //성공
		session.setAttribute("message", "게시글이 등록되었습니다");
		//detail?no=숫자&type=숫자;
		path="detail?no="+boardNo+"&type="+boardCode;
//						detail?no=1000&cp=1&type=2
		
	}else {//작성 실패
		session.setAttribute("message", "게시글 등록에 실패하였습니다.");
		//실패 했을때
		path="write?mode="+mode+"&type="+boardCode;
	}
	resp.sendRedirect(path); //리다이렉트
}

0개의 댓글