반응형
SMALL

게시글에 사진 첨부하기(multipart로 ajax 통신)

멀티파트를 통해 사진을 저장해주자.

[ 만들 기능 ]
1. 네이버 스마트 에디터에 작성한 글과 사진이 DB에 저장됨
2. 저장된 이미지는 게시글 상세보기에서 볼 수 있음

1. 작성한 내용 ajax로 보낼 때 FormData로 보내기

▼ FormData란?

FromData란 ajax로 폼 전송을 가능하게 해주는 FormData 객체입니다.

더보기

FromData란 ajax로 폼 전송을 가능하게 해주는 FormData 객체입니다.
보통은 Ajax로 폼(form 태그) 전송을 할 일이 거의 없습니다.
주로 JSON 구조로 "KEY-VALUE" (키와 값) 구조로 데이터를 전송합니다.

하지만,
form전송이 필요한 경우가 있는데, 이미지를 ajax로 업로드할 때 필요합니다.
이미지는 base64, buffer, 2진 data 형식으로 서버로 전송해도 됩니다.

하지만 추천 드리는 방법은 input[type=file]을 사용해 form(폼)을 통해서 업로드를 하는 것 입니다.
보통, form을 제출하면 action 속성에 의해 지정한 페이지로 이동하면서 데이터를 전송합니다.
ajax는 반대로 제출 버튼을 누르면 기본 폼 동작은 e.preventDefault()  로 멈추고, 페이지 전환 없이 데이터를 전송합니다.

페이지 전환 없이 폼 데이터를 제출 하고 싶을 때 바로 FormData 객체를 사용합니다.

FormData 객체란 ?

 " window.FormData "에 위치합니다.

//html에 form 태그가 있으면 제이쿼리나 자바스크립트로 가져옵니다
var formData1 =new formData($("#form Id")); //제이쿼리인 경우
var formData2 =new formData(document.getElementById("form Id")); //자바스크립트로 가져 올 경우

//html에 form 태그가 없을 때 
//new FromData()로 새로운 객체 생성
var formData = new FormData();
formData.append('name','hyemin');
formData.append('item','hi');
formData.append('item','hello');

append() 메소드로 key-value 값을 하나씩 추가해주면 됩니다.

같은 key를 가진 값을 여러 개 넣을 수 있습니다.
(덮어씌워지지 않고 추가가 됩니다.)
참고로 값은 "문자열"로 자동 변환 됩니다.

* 숫자를 넣어도 문자열이 되고, 배열을 넣어도 콤마로 구분한 문자열이 됩니다. 객체는 넣으면 무시가 됩니다 !!!

formData에 값이 들어있는지 확인하는 방법

// 값이 boolean type으로 반환됩니다.
formData.has('item'); // true 
fromData.has('money'); //false

//이때는, 값의 첫 번째 값이 반환됩니다.
formData.get('item'); // hi 

//값을 모두 가져올 때 배열 형식으로 가져옵ㄴ디ㅏ.
formData.getAll('item'); // ['hi','hello']

has() : has메소드는 해당하는 key가 존재하는 지 확인할 수 있습니다.
get() : get메소드로 직접 가져올 수 있습니다.
( *get은 처음 저장한 값 하나만 불러옵니다.)
getAll() : 해당 key에 매칭되는 value값을 모두 배열로 반환합니다.

formData에 이미지 담기

var formData = new FormData();
formData.append('img',document.getElementById('file input').files[0]);

 이렇게 선언하면, 'img'라는 key값으로 input 값에 담긴 file이 value값으로 들어가게 됩니다.
( 파일이 여러 개면 반복문으로 append를 하면 됩니다. )

* 여러 개를 append할 때 항상 "key" 값은 같아야 여러 파일이 같은 키로 업로드 됩니다. !!
(set을 이용하면 덮어씌워지기 때문에 append를 사용합시다 !!! ) 
또한, form의 속성중 (파일 전송 시) enctype이 반드시 'multipart/form-data'형 이어여만 합니다.

출처 : https://2ham-s.tistory.com/307


 

1) jsp

기존에는 writePost 라는 변수에 각각의 값들을 담아 보내줬는데 이미지를 함께 보내줄 것이므로 FormData에다가 ajax로 전부 담아서 보내주자.

	    /* 버튼 클릭 이벤트 */
	    function submitPost(){

		  oEditors.getById["editorTxt"].exec("UPDATE_CONTENTS_FIELD", []);
		  //content Text 가져오기
		  let content = document.getElementById("editorTxt").value;
		
		  if(content == '<p>&nbsp;</p>') { //내용이 비어있는 경우
		    alert("내용을 입력해주세요.")
		    oEditors.getById["editorTxt"].exec("FOCUS")
		    return;
		  } else {
		    //기존에 writePost 변수에 담아 보내줬던거 주석처리
			 let writePost = {
	       //   title: $("#title")[0].value
	       //   ,thumbnail : document.getElementById("fileName").value
	      //    ,content: content
	        }
		//======= else 구문에서 formData에 데이터 담아 ajax로 보냄 ========	 
				// 등록할 파일 리스트를 formData로 데이터 입력
				var form = $('#form');
				var formData = new FormData(form[0]); //Form 객체 만들기
					formData.append('files',fileObject[0]); //이미지 넣기
					formData.append('title', $("#title")[0].value); //제목넣기
					formData.append('content', content); //내용 넣기
			 
		    //ajax 통신으로 서버로 보내 데이터 저장함
		    $.ajax({
	          url: "postInsertAjax"
	          , data: formData
	          , type: 'POST'
	          , enctype : 'multipart/form-data' //반드시 enctype 을 멀티파트로 설정
	          , contentType : false //false 로 선언 시 content-type 헤더가 multipart/form-data로 전송되게 함
	          , processData : false //false로 선언 시 formData를 string으로 변환하지 않음
	          , success: function(data) {
	            console.log('success')
	            alert('저장되었습니다.')
	            location.href='./myBlogAction.me'
	          }
	          , error: function(jqXHR, textStatus, errorThrown) {
	            console.log(jqXHR);
	            alert('오류가 발생하였습니다.');
	          }
	        })
		  }
		}

주의할 점은 반드시  enctype : 'multipart/form-data' 를 설정해주는 것.

이미지를 미리 볼 수 있는  것도 설정해준다. 파일 input 에 inchange에 걸어 파일 첨부 시 이미지가 보일 수 있도록 설정.

<img id="preview" src="" alt="image" style="width:100px" /> <!--썸네일 볼 곳-->
<input type="file" name="fileName" id='fileName' onchange="readURL(this);"><!--파일첨부-->
/* 이미지 미리보기 */
	    function readURL(input) {
		  if (input.files && input.files[0]) {
		    var reader = new FileReader();
		    reader.onload = function (e) {
		      $('#preview')
		      .attr('src', e.target.result);
		    };
		    reader.readAsDataURL(input.files[0]);
		  }
		}

 

2) Controller

작성한 블로그 포스팅을 ajax로 서버에서 받아 처리한다.

//-------------------- 작성한 블로그 포스팅을 저장하는 postInsertAction 페이지 생성 ---------------------------				
else if (command.equals("/member/postInsertAjax")) {
	action = new PostInsertAction();
	try {
			forward = action.execute(req, resp); //받은 action을 뜯어서 SQL로 보내준다.(서버요청)
	} catch (Exception e) {
			e.printStackTrace();
			System.out.println("포스팅 저장 실패");
	}
}

3) Action

원래라면 ajax로 받아온 데이터를 request.getParameter()를 이용하여 입력할 목록을 설정해줬을 것이다.

하지만 FormData로 가져온 것은 형식이 다르다. MultipartRequest로 가져와야하고 request.getParameter로 가져올 수 없다.

POST 방식에서 request.getParameter()메서드를 WAS에서 알아서 처리할 수 있도록 되어있는 이유는 form에서 method가 POST방식일 때는 디폴트값으로 enctype="application/x-www-form-urlencoded" 옵션이 설정 되어있기 때문에 이를 WAS에서 인식하고 알아서 in/output방식으로 데이터를 처리하기 때문입니다.
따라서 이미지를 위해서 전송하는 경우 enctype가 Multipart로 설정해야하기 때문에 request.getParameter()로 데이터를 불러올 수 없게 됩니다.

▼ MultipartRequest란?

파일 업로드를 직접적으로 담당하는 클래스이며, 파일업로드를 담당하는 생성자 및 여러 메소드를 포함하고 있다.

더보기

MultipartRequest의 생성자

MutlipartRequest클래스는 다음과 같은 생성자를 가지고 있다.

MultipartRequest(javax.servlet.http.HttpServletRequest request,
		 java.lang.String saveDirectory,
                 int maxPostSize,
                 java.lang.String encoding,
                 FileRenamePolicy policy)

다음 표는 생성자의 각 파라미터들에 대한 설명이다.

인자 설명
request MultipartRequest와 연결된 request객체를 의미한다.
saveDirectory 서버 컴퓨터에 파일이 실질적으로 저장될 경로를 의미한다.
maxPostSize 한 번에 업로드할 수 있는 최대 파일 크기를 의미한다.
encoding  파일의 인코딩 방식을 의미한다.
policy 파일 이름 중복 처리를 위한 클래스 객체를 의미한다.

 

MultipartRequest의 메서드

메서드 설명
getParameterNames() 폼에서 전송된 파라미터의 타입이 file이 아닌 파라미터들의 이름들을 Enumeration타입으로 반환한다.
getParameterValues() 폼에서 전송된 파라미터 값들을 배열로 받아온다.
getParameter() request 객체에 있는 지정된 이름의 파라미터 값을 가져온다.
getFileNames() 파일을 여러개 업로드할 경우 타입이 file인 파라미터 이름들을 Enumeration 타입으로 반환한다.
getFilesystemName() 서버에 실제로 업로드된 파일의 이름을 반환한다.
getOriginalFileName() 클라이언트가 업로드한 파일의 원본 이름을 반환한다.
getContentType() 업로드된 파일의 마임 타입을 반환한다.
getFile() 서버에 업로드된 파일 객체 자체를 반환한다.

 

MultipartRequest 객체를 생성할 때엔 생성자를 함께 설정한다.

HttpServletRequest request = request 객체
String saveDirectory =저장될 서버 경로
int maxPostSize = 파일 최대 크기
String encoding = 인코딩 방식
FileRenamePolicy policy = 같은 이름의 파일명 방지 처리
//업로드 파일 사이즈
int fileSize = 5*1024*1024;
//파일 저장 경로
String uploadPath = req.getServletContext().getRealPath("/resources/img/thumbnail");

//멀티파트 객체 생성
MultipartRequest multi = new MultipartRequest(req, uploadPath, fileSize, "UTF-8", new DefaultFileRenamePolicy());

즉, 업로드 파일 사이즈와 파일을 저장할 경로, 인코딩 방식과 파일명 처리 방식까지 함께 생성자로 넣어 객체를 생성한다.

현재 받아온 데이터는 첨부파일, 포스팅 제목, 내용 이렇게 3가지인데 이거 누가 썼는지는 알아야하니, 세션에 있는 ID값까지 해서 받아보자.

//파일 이름 초기화
String fileName  = "";
            
//파일 이름 가져오기
Enumeration<String> names = multi.getFileNames();
if(names.hasMoreElements()) {
   String name = names.nextElement();
   fileName = multi.getFilesystemName(name); //리네임 된 이름(숫자가 붙음) 실제 서버상 저장된 이름
   //  fileName = multi.getOriginalFileName(name); //오리지날 이름
}

//session을 써서 서버 생성함.
HttpSession session = req.getSession();
// 세션에서 id값 가지고 있기. -> 이걸로 post_info의 mem_no를 넣어줄 것임.
String sessionId = (String) session.getAttribute("id");
post.setMEM_ID(sessionId); //id값 넣기
post.setPOST_TITLE(multi.getParameter("title")); //제목 넣기
post.setPOST_THUMBNAIL(fileName); //위에서 가져온 파일 이름만 넣기
post.setPOST_CONTENT(multi.getParameter("content")); //내용 넣기

파일은 이름만 추출해서 DB에 저장해준다. post에 저장된 것들을 서비스로 보내준다.

PostInsertService postInsertService = new PostInsertService();
boolean result = postInsertService.insertPost(post); //vo에서 받은 변수 보내줌.

 

▼ PostInsertAction.java 소스 전체보기

더보기
package action;

import java.util.Enumeration;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import com.oreilly.servlet.MultipartRequest;
import com.oreilly.servlet.multipart.DefaultFileRenamePolicy;

import svc.PostInsertService;
import vo.ActionForward;
import vo.PostBean;

/* 인터페이스를 함께 추가했다. 작성한 포스트 저장 처리를 위한 클래스이다.*/
public class PostInsertAction implements Action { // Action을 implements 해줌

	@Override
	public ActionForward execute(HttpServletRequest req, HttpServletResponse resp) throws Exception {

		PostBean post = new PostBean(); //vo에 선언한 변수들 import한거.
		//작성한 게시글 내용을 저장하고 DB로 전달.

		ActionForward forward = null;

        
  
		//------- 파일 업로드 구현 --------------------------------------
	    //업로드 파일 사이즈
        int fileSize = 5*1024*1024;
     
        String uploadPath = req.getServletContext().getRealPath("/resources/img/thumbnail");  
        System.out.println("uploadpath는?  "+uploadPath);
        try {
            //파일업로드
            MultipartRequest multi = new MultipartRequest(req, uploadPath, fileSize, "UTF-8", new DefaultFileRenamePolicy());
            /*HttpServletRequest request = request 객체			
			String saveDirectory =저장될 서버 경로			
			int maxPostSize = 파일 최대 크기			
			String encoding = 인코딩 방식			
			FileRenamePolicy policy = 같은 이름의 파일명 방지 처리*/

            //파일 이름 초기화
            String fileName  = "";
            
            //파일 이름 가져오기
            Enumeration<String> names = multi.getFileNames();
           
            if(names.hasMoreElements()) {
                String name = names.nextElement();
                fileName = multi.getFilesystemName(name); //리네임 된 이름(숫자가 붙음) 실제 서버상 저장된 이름
              //  fileName = multi.getOriginalFileName(name); //오리지날 이름
            }
            
            
            
    		//session을 써서 서버 생성함.
    		HttpSession session = req.getSession();
    		// 세션에서 id값 가지고 있기. -> 이걸로 post_info의 mem_no를 넣어줄 것임.
    		String sessionId = (String) session.getAttribute("id");
            System.out.println("sessionId "+sessionId);
    		
    		post.setMEM_ID(sessionId);
    		//입력 목록 적어주기(vo에서 받아옴.)
    		post.setPOST_TITLE(multi.getParameter("title"));
//    		post.setPOST_THUMBNAIL(multi.getParameter("thumbnail"));
    		post.setPOST_THUMBNAIL(fileName);
    		post.setPOST_CONTENT(multi.getParameter("content"));
    		
    		
    		/*
    		POST 방식에서 request.getParameter()메서드를
			WAS에서 알아서 처리할 수 있도록 되어있는 이유는
			form에서 method가 POST방식일 때는 디폴트값으로
			enctype="application/x-www-form-urlencoded" 옵션이 설정 되어있기 때문에
			이를 WAS에서 인식하고 알아서 in/output방식으로 데이터를 처리하기 때문입니다.
			따라서. 이미지를 위해서 전송하는 경우 enctype가 Multipart로 설정해야하기 때문에
			request.getParameter()로 데이터를 불러올 수 없게 됩니다.
    		*/
    		
    		
    
    		PostInsertService postInsertService = new PostInsertService();
    		boolean result = postInsertService.insertPost(post); //vo에서 받은 변수 보내줌.
    		//게시글 저장이 잘 되었는지 여부
            
//         
            if(result) {
             //   forward.setRedirect(true);
             //   forward.setPath("myBlogAction.me");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return forward;
    }

		//-------------------- 직전 포스팅 로직 -------------------------		
		
		
//		//session을 써서 서버 생성함.
//		HttpSession session = req.getSession();
//		// 세션에서 id값 가지고 있기. -> 이걸로 post_info의 mem_no를 넣어줄 것임.
//		String sessionId = (String) session.getAttribute("id");
//		
//		post.setMEM_ID(sessionId);
//		//입력 목록 적어주기(vo에서 받아옴.)
//		post.setPOST_TITLE(req.getParameter("title"));
//		post.setPOST_THUMBNAIL(req.getParameter("thumbnail"));
//		post.setPOST_CONTENT(req.getParameter("content"));
//		
//
//		PostInsertService postInsertService = new PostInsertService();
//		postInsertService.insertPost(post); //vo에서 받은 변수 보내줌.
//		//게시글 저장이 잘 되었는지 여부
		
		//ajax 통신이라 서버에서 안먹히나? 왜 이게 안되지...
		
//		System.out.println(InsertResult);
//		if (InsertResult) { //게시글 저장 후 내 블로그로 이동
//
//			resp.setContentType("text/html;charset=UTF-8");
//			PrintWriter out = resp.getWriter();
//			out.println("<script>");
//			out.println("alert('저장되었습니다.')");
//			out.println("location.href='./myBlogAction.me");
//			out.println("</script>");
//		} else { //게시글 저장 실패 시
//			resp.setContentType("text/html;charset=UTF-8");
//			PrintWriter out = resp.getWriter(); //위에 있음
//			out.println("<script>");
//			out.println("alert('게시글 저장 실패')");
//			out.println("history.back()");
//			out.println("</script>");
//		}
		
//		return forward;
//	}

}

 

4) Service

똑같이 커넥션풀 열어서 DB 연결해주고

/* 게시글을 DB에 저장하는 역할을 담당하는 클래스(Service) */
public class PostInsertService {
	
	public boolean insertPost(PostBean post) {
		
		boolean joinSuccess = false; //저장완료 기본값 false
		PostDAO postDAO = PostDAO.getInstance();
		// 객체에 저장된 고객 정보를 DB로 전달하는 클래스.
		// DAO : Data Access Object
		Connection con = getConnection();
		// DB와 jsp간의 연결을 해주는 것을 담당함.
		postDAO.setConnection(con);
		int insertCount = postDAO.insertPost(post);
		// DB추가 쿼리를 메소드로 구현.

		if (insertCount > 0) {
			joinSuccess = true;
			// 정상 처리 되었음을 호출했던 곳으로 리턴.
			commit(con); //DB 명령 완료 확인
		} else {
			rollback(con);
			//만약 제대로 동작하지 않았다면
			//쿼리문에 대해서 취소(rollback)
		}
		close(con);
		//연결되지 않았으면 연결 해제(DB와 Connection의 연결 해제)

		return joinSuccess;
	}
}

5) DAO

쿼리에 원하는 값들을 넣어 DB에 이미지 정보와 작성한 포스팅 내용을 저장해준다.(아직 영상은 빼봄)

		//=========================== 게시글 저장하는 SQL로직 ===============================
		// PostInsertService에서  게시글 저장할 때 DB와 JSP를 연결할 때 인자로 쓰임.
		public int insertPost(PostBean post) {
			//게시글 저장할 때 SQL문(DB 이름 확인하기***)
			String sql = 
				"INSERT INTO post_info("
						+ "post_no,"
						+ "mem_no,"
						+ "post_title,"
						+ "post_thumbnail,"
						+ "POST_VIDEO,"
						+ "POST_CONTENT,"
						+ "VISIT_CNT,"
						+ "POST_UPLOADTIME"
				+ ") "
				+ "VALUES( "
						+ "(SELECT NVL(MAX(post_no), 0) + 1 FROM post_info)," //post_no
						+ "(SELECT mem_no FROM memberinfo WHERE mem_id = ?)," //mem_no 찾기
						+ "?," //post_title
						+ "?," //post_thumbnail
						+ "'비디오.avi',"
						+ "?," //POST_CONTENT
						+ "0," //VISIT_CNT
						+ "TRUNC(SYSDATE)" //POST_UPLOADTIME
				+ ")";
			
			int insertCount=0;

			try {
				pstmt = con.prepareStatement(sql); 
				//prepareStatement : SQL문 실행하는 기능을 갖는 객체(변수는 ?로, setString으로 아래에 지정함.)
				pstmt.setString(1, post.getMEM_ID()); //sessionId로 mem_no 찾기
				pstmt.setString(2, post.getPOST_TITLE());
				pstmt.setString(3, post.getPOST_THUMBNAIL());
				//pstmt.setInt(4, post.getPOST_VIDIO());
				pstmt.setString(4, post.getPOST_CONTENT());
				insertCount=pstmt.executeUpdate(); //executeUpdate : 데이터베이스 변경할 때
				//select는 executeQuery()를 사용한다.
				// insert, update, delete는 executeUpdate()를 사용한다.
				//정상적으로 된다면 insertCount가 1이 된다.
			} catch (Exception ex) {
				System.out.println("게시글 저장 안됨" + ex);
				

			} finally {
				close(pstmt); // import static db.JdbcUtil.*;
			}
			return insertCount;
		}

 

결과) 새글 작성 - 이미지와 함께 포스팅 저장

다음은 게시글 수정 페이지를 만들어서 update도 해보겠음.

 ▼ postEdit.jsp 소스 전체보기

더보기
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ page import="vo.MemberBean"%>
<%@ page import="java.util.*"%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!-- 빨간줄 그어지는데 jstl 설치 안해서 그럼. lib에 넣어준다. jsp 확장태그로 c로 쓴다.-->


<!DOCTYPE html>
<html lang="en">
    <head>
    <!-- 참고 : https://beforb.tistory.com/53 -->
    <!-- 참고 : https://www.wrapuppro.com/programing/view/ZPLdxEBiyJG38IG -->
    <!-- 에디터 : Naver SmartEditor 2.0 -->
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
        <meta name="description" content="" />
        <meta name="author" content="" />
        <title>게시글 작성</title>
        <!-- 제이쿼리 -->
		<script type="text/javascript" src="https://code.jquery.com/jquery-3.4.1.js"></script>
        <!-- Favicon-->
       <!-- <link rel="icon" type="image/x-icon" href="assets/favicon.ico" /> -->
        <!-- Core theme CSS (includes Bootstrap)-->
         <link href="../resources/css/mainPage.css" rel="stylesheet" />
      <script type="text/javascript" src="../resources/static/smarteditor/js/HuskyEZCreator.js" charset="utf-8"></script>    </head>
    <body>
        <!-- Responsive navbar-->
        <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
            <div class="container">
                <a class="navbar-brand" href="#!">N Blog - 새 게시글 작성</a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button>
                <div class="collapse navbar-collapse" id="navbarSupportedContent">
                    <ul class="navbar-nav ms-auto mb-2 mb-lg-0">
                        <li class="nav-item"><a class="nav-link" href="#">Home</a></li>
                        <li class="nav-item"><a class="nav-link" href="#!">About</a></li>
                        <li class="nav-item"><a class="nav-link" href="#!">Contact</a></li>
                        <li class="nav-item"><a class="nav-link active" aria-current="page" href="#">Blog</a></li>
                    </ul>
                </div>
            </div>
        </nav>
        <!-- Page content-->
        <div class="container mt-5">
            <div class="row">
                    <!-- 포스트 추가하는 곳-->
                    <!-- Naver SmartEditor 2.8.2를 사용하였습니다. -->
                    <!-- 파일 전송을 위해 encType = "multipart/form-data" -->
				    <form action="insertStudentInfoForm" id="form"  enctype="multipart/form-data" method="post">
				    	<!-- 제목 -->
				      <input type="text" placeholder="제목을 입력하세요" id="title" style='width: 600px'>
				      
				      <div id="smarteditor">
				      <!-- 우선 멤버와 썸네일은 정적으로 데이터를 넣어둠. 
				      <input type="hidden" value="일단 썸네일임" id="thumbnail">-->
				      
				        <textarea name="editorTxt" id="editorTxt" 
				                  rows="20" cols="10" 
				                  placeholder="&#13;&#10;내용을 입력하세요"
				                  style='width: 600px'></textarea>
				                  
				      </div>
				      <img id="preview" src="" alt="image" style="width:100px" />
				      <input type="file" name="fileName" id='fileName' onchange="readURL(this);">
				      <input type="button" value ="저장" onclick="submitPost()"/>
				    </form>
              <!-- 포스트 추가하는 곳-->
            </div>
        </div>
        <!-- Footer-->
        <footer class="py-5 bg-dark">
            <div class="container"><p class="m-0 text-center text-white">Copyright &copy; Your Website 2022</p></div>
        </footer>
        <!-- Bootstrap core JS-->
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
        <!-- Core theme JS-->
     <!--   <script src="js/scripts.js"></script>-->
	     <script>
	     
	     /* 에디터 설정 */
	    let oEditors = [];
	
	    smartEditor = function() {
	      nhn.husky.EZCreator.createInIFrame({
	        oAppRef: oEditors,
	        elPlaceHolder: "editorTxt",
	        sSkinURI: "../resources/static/smarteditor/SmartEditor2Skin.html",
	        fCreator: "createSEditor2"
	      })
	    }
	
	    $(document).ready(function() {
	      smartEditor()
	    })
	    
	    
	    
	    //첨부한 파일 내용 전역변수로 가지고 있기
	    let fileObject = null;
	    //파일 선택 시 파일 내용 변수에 넣기
 	    $(document).ready(function() {
			$("#fileName").bind('change', function() {
				fileObject = this.files;
				//this.files[0].size gets the size of your file.
				//alert(this.files[0].size);
			});
		}); 

	    /* 버튼 클릭 이벤트 */
	    function submitPost(){

		  oEditors.getById["editorTxt"].exec("UPDATE_CONTENTS_FIELD", []);
		  //content Text 가져오기
		  let content = document.getElementById("editorTxt").value;
		
		  if(content == '<p>&nbsp;</p>') { //비어있는 경우
		    alert("내용을 입력해주세요.")
		    oEditors.getById["editorTxt"].exec("FOCUS")
		    return;
		  } else {
		    //console.log(content);
			 let writePost = {
	       //   title: $("#title")[0].value
	       //   ,thumbnail : document.getElementById("fileName").value
	      //    ,content: content
	        }
			 
			 
				// 등록할 파일 리스트를 formData로 데이터 입력
				var form = $('#form');
				var formData = new FormData(form[0]);
					formData.append('files',fileObject[0]);
					formData.append('title', $("#title")[0].value);
					formData.append('content', content);
			 console.log(fileObject[0]);
			 
		    //ajax 통신으로 서버로 보내 데이터 저장함
		    $.ajax({
	          url: "postInsertAjax"
	          , data: formData
	          , type: 'POST'
	          , enctype : 'multipart/form-data'
	          , contentType : false //false 로 선언 시 content-type 헤더가 multipart/form-data로 전송되게 함
	          , processData : false //false로 선언 시 formData를 string으로 변환하지 않음
	          , success: function(data) {
	            console.log('success')
	            alert('저장되었습니다.')
	            location.href='./myBlogAction.me'
	          }
	          , error: function(jqXHR, textStatus, errorThrown) {
	            console.log(jqXHR)
	            alert('오류가 발생하였습니다.')
	          }
	        })
	        
		  }
		}
	    
	    
	    /* 이미지 미리보기 */
	    function readURL(input) {
		  if (input.files && input.files[0]) {
		    var reader = new FileReader();
		    reader.onload = function (e) {
		      $('#preview')
		      .attr('src', e.target.result);
		    };
		    reader.readAsDataURL(input.files[0]);
		  }
		}
	    
	    
	  </script>
     
    </body>
</html>

 

▼ 진행중인 gitHub 링크를 남깁니다.

 

GitHub - k-haein/BlogPrj: 회원가입/로그인/게시글 등록 등의 블로그를 처음부터 만듭니다.

회원가입/로그인/게시글 등록 등의 블로그를 처음부터 만듭니다. Contribute to k-haein/BlogPrj development by creating an account on GitHub.

github.com

 

반응형
LIST
반응형
SMALL

Naver SmartEditor 2.0으로 게시글 만들기(+DB연결)

네이버 블로그처럼 블로그 내용을 다채롭게 작성하고 싶었다. 찾아보니 Naver SmartEditor가 오픈소스로 나와있길래 사용하기로 했다.

현재 네이버 블로그에 적용된 3.0은 오픈소스로 공개가 되어있지 않고, 2.8까지 사진 업로드가 가능하다고 해서 2.8로 사용하기로 함.

[ 만들 기능 ]
1. 게시글 작성 페이지에 발행 누르면 DB 저장
2. 저장 후 게시글 상세보기 페이지에서 읽을 수 있도록 연결

1. Naver SmartEditor 2.0 설치하기

요 블로그 참고했다.(https://beforb.tistory.com/53)

스마트 에디터 2.8.2 다운로드 : https://github.com/naver/smarteditor2/releases/tag/v2.8.2.3

 

Release v2.8.2.3: archive v2.8.2.4259f59 · naver/smarteditor2

2.8.2.3 보안 패치 file_uploader_html5.php의 null byte injection 취약점 추가 보완 file_uploader.php의 리다이렉트 취약점 보완 sample.php에서 XSS filtering을 위해 HTMLPurifier라이브러리를 적용 sample.php -> sample/viewer/in

github.com

resources > static > smarteditor 에 다운받은 파일들을 넣어주었다.

resources > static > smarteditor 붙여넣기

스마트 에디터를 출력할 페이지에 js를 import 해준다.

<script type="text/javascript" src="../resources/static/smarteditor/js/HuskyEZCreator.js" charset="utf-8"></script>

포스트 추가할 곳에 textArea 설정해주자.

<div class="row">
    <!-- 포스트 추가하는 곳-->
    <!-- Naver SmartEditor 2.8.2를 사용하였습니다. -->
    <h3>Naver Smart Editor 2.0</h3>
    <form action="insertStudentInfoForm" method="post">
        <div id="smarteditor">
            <textarea
                name="editorTxt"
                id="editorTxt"
                rows="20"
                cols="10"
                placeholder="내용을 입력해주세요"
                style="width: 500px"></textarea>
        </div>
        <input type="button" value="내용콘솔에" onclick="submitPost()"/>
    </form>
    <!-- 포스트 추가하는 곳-->
</div>

스크립트를 설정해준다. 아까 resources > static > smarteditor 경로에 있던 SmartEditor2Skin.html 파일이 에디터 화면이다. sSkinURI 경로를 주의해서 스크립트를 작성해준다.

	     /* 에디터 설정 */
	    let oEditors = [];
	
	    smartEditor = function() {
	      console.log("Naver SmartEditor")
	      nhn.husky.EZCreator.createInIFrame({
	        oAppRef: oEditors,
	        elPlaceHolder: "editorTxt",
	        sSkinURI: "../resources/static/smarteditor/SmartEditor2Skin.html",
	        fCreator: "createSEditor2"
	      })
	    }
	
	    $(document).ready(function() {
	      smartEditor()
	    })

 

++추가) html도 못읽길래 sever>web.xml에 추가해줬다.

    <!-- The mapping for the default servlet -->
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>/</url-pattern>
        <!-- 아래에 js 및 css 확장자 추가 -->
        <url-pattern>*.js</url-pattern>
        <url-pattern>*.css</url-pattern>
        <url-pattern>*.jpg</url-pattern>
        <url-pattern>*.ico</url-pattern>
        <url-pattern>*.gif</url-pattern>
        <url-pattern>*.png</url-pattern>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>

 

저장 버튼 누르면 DB로 스마트에디터에 작성한 내용이 저장되야하므로 일단 콘솔에서 띄워본다. 버튼클릭 이벤트를 만들어서 console.log를 찍어보자.

 /* 버튼 클릭 이벤트 */
	    submitPost = function() {
		  oEditors.getById["editorTxt"].exec("UPDATE_CONTENTS_FIELD", [])
		  let content = document.getElementById("editorTxt").value
		
		  //if(content == '') {
            if(content == '<p>&nbsp;</p>') { //비어있어도 기본 P태그가 붙더라.
		    alert("내용을 입력해주세요.")
		    oEditors.getById["editorTxt"].exec("FOCUS")
		    return
		  } else {
		    console.log(content)
		  }
		}

결과창을 확인하면 잘 나온다.

결과

 

2. 스마트 에디터에 작성한 내용 ajax로 DB에 저장해주기

 1) jsp

일단 제목과 내용을 제대로 저장시켜주는 것이 중요하므로, 썸네일과 기타 다른 데이터들은 정적데이터로 넣어주고 연결부터 하기로 함.

   /* 버튼 클릭 이벤트 */
	    function submitPost(){

		  oEditors.getById["editorTxt"].exec("UPDATE_CONTENTS_FIELD", []);
		  //content Text 가져오기
		  let content = document.getElementById("editorTxt").value;
		
		  if(content == '<p>&nbsp;</p>') { //비어있는 경우
		    alert("내용을 입력해주세요.")
		    oEditors.getById["editorTxt"].exec("FOCUS")
		    return;
		  } else {
		    //console.log(content);
			 let writePost = {
	          title: $("#title")[0].value
	          ,content: content
	        }
			 
		    //ajax 통신으로 서버로 보내 데이터 저장함
		    $.ajax({
	          url: "postInsertAjax"
	          , data: writePost
	          , type: 'POST'
	         , success: function(data) {
	            console.log('success')
	            alert('저장되었습니다.')
	            location.href='./myBlogAction.me'
	          }
	          , error: function(jqXHR, textStatus, errorThrown) {
	            console.log(jqXHR)
	            alert('오류가 발생하였습니다.')
	          }
	        })
		  }
		}

간단하게 writePost 라는 변수를 선언해서 여기에 title이랑 content를 key,value 형태로 담아서 보내주었다.

 2) controller

[새글쓰기] 버튼을 눌렀을 때에도 바로 jsp 페이지가 아니라 me로 끝나는 페이지로 이동하도록 설정해주자.

또한 입력한 데이터를 DB에 저장해주는 Action 페이지도 만든다.

//-------------------- 새글작성 버튼 누르면 게시글 작성 페이지로 이동 ---------------------------
else if(command.equals("/member/postInsert.me")) {
    //게시글 작성 페이지로 이동함
    forward = new ActionForward();
    //객체 생성
    forward.setRedirect(false);
    //이동 허락 안함
    forward.setPath( "./postEdit.jsp"))
    //거기 주소는 postEdit.jsp으로 해라.(이동할 주소 저장)
}
//-------------------- 작성한 블로그 포스팅을 저장하는 postInsertAction 페이지 생성 ---------------------------				
else if (command.equals("/member/postInsertAjax")) {
    action = new PostInsertAction();
    try {
        forward = action.execute(req, resp); //받은 action을 뜯어서 SQL로 보내준다.(서버요청)
    }
    catch (Exception e) {
        e.printStackTrace();
        System.out.println( "포스팅 저장 실패"))
    }
}

 3) Action

postInsertAjax 로 접속했을 때 이동할 PostInsertAction 페이지를 만들어준다.

/* 인터페이스를 함께 추가했다. 작성한 포스트 저장 처리를 위한 클래스이다.*/
public class PostInsertAction implements Action { // Action을 implements 해줌

	@Override
	public ActionForward execute(HttpServletRequest req, HttpServletResponse resp) throws Exception {

		PostBean post = new PostBean(); //vo에 선언한 변수들 import한거.
		//작성한 게시글 내용을 저장하고 DB로 전달.

		ActionForward forward = null;

		//session을 써서 서버 생성함.
		HttpSession session = req.getSession();
		// 세션에서 id값 가지고 있기. -> 이걸로 post_info의 mem_no를 넣어줄 것임.
		String sessionId = (String) session.getAttribute("id");
		
		post.setMEM_ID(sessionId);
		//입력 목록 적어주기(vo에서 받아옴.)
		post.setPOST_TITLE(req.getParameter("title"));
		post.setPOST_THUMBNAIL(req.getParameter("thumbnail"));
		post.setPOST_CONTENT(req.getParameter("content"));
		
		PostInsertService postInsertService = new PostInsertService();
		boolean InsertResult = postInsertService.insertPost(post); //vo에서 받은 변수 보내줌.
		//게시글 저장이 잘 되었는지 여부
		       
		//ajax 통신이라 서버에서 안먹히나? 이 아래로 안됨...
		System.out.println(InsertResult);
		if (InsertResult) { //게시글 저장 후 내 블로그로 이동

			resp.setContentType("text/html;charset=UTF-8");
			PrintWriter out = resp.getWriter();
			out.println("<script>");
			out.println("alert('저장되었습니다.')");
			out.println("location.href='./myBlogAction.me");
			out.println("</script>");
		} else { //게시글 저장 실패 시
			resp.setContentType("text/html;charset=UTF-8");
			PrintWriter out = resp.getWriter(); //위에 있음
			out.println("<script>");
			out.println("alert('게시글 저장 실패')");
			out.println("history.back()");
			out.println("</script>");
		}
		return forward;
	}
}

4) Service

DB 연결한다.

/* 게시글을 DB에 저장하는 역할을 담당하는 클래스(Service) */
public class PostInsertService {
	
	public boolean insertPost(PostBean post) {
		
		boolean joinSuccess = false; //저장완료 기본값 false
		PostDAO postDAO = PostDAO.getInstance();
		// 객체에 저장된 고객 정보를 DB로 전달하는 클래스.
		// DAO : Data Access Object
		Connection con = getConnection();
		// DB와 jsp간의 연결을 해주는 것을 담당함.
		postDAO.setConnection(con);
		int insertCount = postDAO.insertPost(post);
		// DB추가 쿼리를 메소드로 구현.

		if (insertCount > 0) {
			joinSuccess = true;
			// 정상 처리 되었음을 호출했던 곳으로 리턴.
			commit(con); //DB 명령 완료 확인
		} else {
			rollback(con);
			//만약 제대로 동작하지 않았다면
			//쿼리문에 대해서 취소(rollback)
		}
		close(con);
		//연결되지 않았으면 연결 해제(DB와 Connection의 연결 해제)

		return joinSuccess;
	}
}

4) DAO

update를 한다. 받아온 데이터는 두개이고 나머지는 나중에 세팅해줄 것이므로 정적으로 넣어준다.

//=========================== 게시글 저장하는 SQL로직 ===============================
		// PostInsertService에서  게시글 저장할 때 DB와 JSP를 연결할 때 인자로 쓰임.
		public int insertPost(PostBean post) {
			//게시글 저장할 때 SQL문(DB 이름 확인하기***)
			String sql = 
				"INSERT INTO post_info("
						+ "post_no,"
						+ "mem_no,"
						+ "post_title,"
						+ "post_thumbnail,"
						+ "POST_VIDEO,"
						+ "POST_CONTENT,"
						+ "VISIT_CNT,"
						+ "POST_UPLOADTIME"
				+ ") "
				+ "VALUES( "
						+ "(SELECT NVL(MAX(post_no), 0) + 1 FROM post_info)," //post_no
						+ "(SELECT mem_no FROM memberinfo WHERE mem_id = ?)," //mem_no 찾기
						+ "?," //post_title
						+ "'섬네일.jpg'," //post_thumbnail
						+ "'비디오.avi',"
						+ "?," //POST_CONTENT
						+ "0," //VISIT_CNT
						+ "TRUNC(SYSDATE)" //POST_UPLOADTIME
				+ ")";
			
			int insertCount=0;

			try {
				pstmt = con.prepareStatement(sql); 
				//prepareStatement : SQL문 실행하는 기능을 갖는 객체(변수는 ?로, setString으로 아래에 지정함.)
				pstmt.setString(1, post.getMEM_ID()); //sessionId로 mem_no 찾기
				pstmt.setString(2, post.getPOST_TITLE());
				pstmt.setString(3, post.getPOST_CONTENT());

				insertCount=pstmt.executeUpdate(); //executeUpdate : 데이터베이스 변경할 때
				//select는 executeQuery()를 사용한다.
				// insert, update, delete는 executeUpdate()를 사용한다.
				//정상적으로 된다면 insertCount가 1이 된다.
				System.out.println("게시글 저장2");
			} catch (Exception ex) {
				System.out.println("게시글 저장 안됨" + ex);
				

			} finally {
				close(pstmt); // import static db.JdbcUtil.*;
			}
			return insertCount;
		}

Update 및 delete 등 데이터베이스를 변경할 때에는 executeUpdate를 사용한다.

SQL문을 뜯어보자.

a) post_no : 최댓값 +1 을 한다.

"(SELECT NVL(MAX(post_no), 0) + 1 FROM post_info)," //post_no

b) mem_no :  세션에 있는 id로 찾아서 넣어줬다.

"(SELECT mem_no FROM memberinfo WHERE mem_id = ?)," //mem_no 찾기

4) VISIT_CNT:  기본값 0이라 그냥 0 넣어줌.

5) POST_UPLOADTIME:  현재 날짜 넣어줌

"TRUNC(SYSDATE)" //POST_UPLOADTIME

DB 설정값 다시 확인


다음은 이 ajax 통신을 멀티파트로 해서 이미지도 함께 업로드 해주겠다.

 ▼ postEdit.jsp 소스 전체보기

더보기
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ page import="vo.MemberBean"%>
<%@ page import="java.util.*"%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!-- 빨간줄 그어지는데 jstl 설치 안해서 그럼. lib에 넣어준다. jsp 확장태그로 c로 쓴다.-->


<!DOCTYPE html>
<html lang="en">
    <head>
    <!-- 참고 : https://beforb.tistory.com/53 -->
    <!-- 참고 : https://www.wrapuppro.com/programing/view/ZPLdxEBiyJG38IG -->
    <!-- 에디터 : Naver SmartEditor 2.0 -->
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
        <meta name="description" content="" />
        <meta name="author" content="" />
        <title>게시글 작성</title>
        <!-- 제이쿼리 -->
		<script type="text/javascript" src="https://code.jquery.com/jquery-3.4.1.js"></script>
        <!-- Favicon-->
       <!-- <link rel="icon" type="image/x-icon" href="assets/favicon.ico" /> -->
        <!-- Core theme CSS (includes Bootstrap)-->
         <link href="../resources/css/mainPage.css" rel="stylesheet" />
      <script type="text/javascript" src="../resources/static/smarteditor/js/HuskyEZCreator.js" charset="utf-8"></script>    </head>
    <body>
        <!-- Responsive navbar-->
        <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
            <div class="container">
                <a class="navbar-brand" href="#!">N Blog - 새 게시글 작성</a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button>
                <div class="collapse navbar-collapse" id="navbarSupportedContent">
                    <ul class="navbar-nav ms-auto mb-2 mb-lg-0">
                        <li class="nav-item"><a class="nav-link" href="#">Home</a></li>
                        <li class="nav-item"><a class="nav-link" href="#!">About</a></li>
                        <li class="nav-item"><a class="nav-link" href="#!">Contact</a></li>
                        <li class="nav-item"><a class="nav-link active" aria-current="page" href="#">Blog</a></li>
                    </ul>
                </div>
            </div>
        </nav>
        <!-- Page content-->
        <div class="container mt-5">
            <div class="row">
                    <!-- 포스트 추가하는 곳-->
                    <!-- Naver SmartEditor 2.8.2를 사용하였습니다. -->
                    
				    <form action="insertStudentInfoForm" method="post">
				    	<!-- 제목 -->
				      <input type="text" placeholder="제목을 입력하세요" id="title" style='width: 600px'>
				      
				      <div id="smarteditor">
				      <!-- 우선 멤버와 썸네일은 정적으로 데이터를 넣어둠. -->
				      <input type="hidden" value="일단 썸네일임" id="thumbnail">
				      
				        <textarea name="editorTxt" id="editorTxt" 
				                  rows="20" cols="10" 
				                  placeholder="&#13;&#10;내용을 입력하세요"
				                  style='width: 600px'></textarea>
				                  
				      </div>
				      <input type="button" value ="저장" onclick="submitPost()"/>
				    </form>
                    
                
              <!-- 포스트 추가하는 곳-->
            </div>
        </div>
        <!-- Footer-->
        <footer class="py-5 bg-dark">
            <div class="container"><p class="m-0 text-center text-white">Copyright &copy; Your Website 2022</p></div>
        </footer>
        <!-- Bootstrap core JS-->
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
        <!-- Core theme JS-->
     <!--   <script src="js/scripts.js"></script>-->
	     <script>
	     
	     /* 에디터 설정 */
	    let oEditors = [];
	
	    smartEditor = function() {
	      nhn.husky.EZCreator.createInIFrame({
	        oAppRef: oEditors,
	        elPlaceHolder: "editorTxt",
	        sSkinURI: "../resources/static/smarteditor/SmartEditor2Skin.html",
	        fCreator: "createSEditor2"
	      })
	    }
	
	    $(document).ready(function() {
	      smartEditor()
	    })
	    
	    
	    /* 버튼 클릭 이벤트 */
	    function submitPost(){
	 
		  oEditors.getById["editorTxt"].exec("UPDATE_CONTENTS_FIELD", []);
		  //content Text 가져오기
		  let content = document.getElementById("editorTxt").value;
			console.log(content);
		  if(content == '<p>&nbsp;</p>') { //비어있는 경우
		    alert("내용을 입력해주세요.")
		    oEditors.getById["editorTxt"].exec("FOCUS")
		    return;
		  } else {
		    //console.log(content);
			 let writePost = {
	          title: $("#title")[0].value
	          ,content: content
	        }
			 console.log(writePost);
		    //ajax 통신으로 서버로 보내 데이터 저장함
		    $.ajax({
	          url: "postInsertAjax"
	          , data: writePost
	          , success: function(data) {
	            console.log('success')
	            alert('저장되었습니다.')
	            location.href='./myBlogAction.me'
	          }
	          , error: function(jqXHR, textStatus, errorThrown) {
	            console.log(jqXHR)
	            alert('오류가 발생하였습니다.')
	          }
	        })
	        
		  }
		}
	    
	  </script>
    </body>
</html>

 

▼ 진행중인 gitHub 링크를 남깁니다.

 

GitHub - k-haein/BlogPrj: 회원가입/로그인/게시글 등록 등의 블로그를 처음부터 만듭니다.

회원가입/로그인/게시글 등록 등의 블로그를 처음부터 만듭니다. Contribute to k-haein/BlogPrj development by creating an account on GitHub.

github.com

 

반응형
LIST
반응형
SMALL

게시글 목록 클릭 시 상세페이지 연결하기

게시글 목록을 클릭하면 해당 게시글로 이동하는 기능을 구현해보자.

[ 만들 기능 ]
1. 목록에 있는 게시글 클릭 시 postNo를 넘겨줌
2. 해당 postNo에 해당하는 상세페이지로 이동
3. 해당 상세페이지에 글쓴이의 정보가 위젯에 뜨게 하기

1. 메인페이지에서 게시글 클릭 시 해당 postNo 보내주기

상세보기 페이지로 파라미터 postno를 함께 넘겨준다.

<c:forEach var="postList" items="${postList}">
    <!-- Featured blog post -->
    <div class="card mb-4">
        <div class="card-body">
            <a href="#!"><img class="card-img-top" src="../resources/img/thumbnail/${postList.POST_THUMBNAIL}" alt="..."/></a>
            <div class="small text-muted">${postList.POST_UPLOADTIME}</div>
            <h2 class="card-title">${postList.POST_TITLE} ----- ${postList.MEM_NO}</h2>
            <p class="card-text">${postList.POST_CONTENT}</p>
            <!-- 상세보기 페이지로 파라미터 postno를 함께 넘겨준다. -->
            <a class="btn btn-primary" href="postViewAction.me?postno=${postList.POST_NO}">Read more →</a>
        </div>
    </div>
</c:forEach>

2. 상세페이지에 게시글 내용 출력

부트스트랩 이용(출처 : https://startbootstrap.com/template/blog-post)

부트스트랩으로 가져온 페이지에 게시글 목록 띄운 것과 동일한 방법으로 상세페이지를 띄운다.

1) JSP

<header class="mb-4">
	<!-- Post title-->
	<h1 class="fw-bolder mb-1">${postObj.POST_TITLE}</h1>
	<!-- Post meta content-->
	<div class="text-muted fst-italic mb-2">${postObj.POST_UPLOADTIME} by ${postObj.MEM_NO}</div>
	<!-- Post categories-->
	<a class="badge bg-secondary text-decoration-none link-light" href="#!">Web Design</a>
	<a class="badge bg-secondary text-decoration-none link-light" href="#!">Freebies</a>
</header>
<!-- Preview image figure-->
<figure class="mb-4"><img class="img-fluid rounded" src="../resources/img/thumbnail/${postObj.POST_THUMBNAIL}" alt="..." /></figure>
<!-- Post content-->
<section class="mb-5">
	<p class="fs-5 mb-4">${postObj.POST_CONTENT}</p>
	<p class="fs-5 mb-4">The universe is large and old, and the ingredients for life as we know it are everywhere, so there's no reason to think that Earth would be unique in that regard. Whether of not the life became intelligent is a different question, and we'll see if we find that.</p>
	<p class="fs-5 mb-4">If you get asteroids about a kilometer in size, those are large enough and carry enough energy into our system to disrupt transportation, communication, the food chains, and that can be a really bad day on Earth.</p>
	<h2 class="fw-bolder mb-4 mt-5">I have odd cosmic thoughts every day</h2>
	<p class="fs-5 mb-4">For me, the most fascinating interface is Twitter. I have odd cosmic thoughts every day and I realized I could hold them to myself or share them with people who might be interested.</p>
	<p class="fs-5 mb-4">Venus has a runaway greenhouse effect. I kind of want to know what happened there because we're twirling knobs here on Earth without knowing the consequences of it. Mars once had running water. It's bone dry today. Something bad happened there as well.</p>
</section>
</article>

게시글 리스트와 구분해주려고 postObj라는 이름으로 쓰기로 함.

▼ blogPost.jsp 전체

더보기
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ page import="vo.MemberBean"%>
<%@ page import="java.util.*"%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!-- 빨간줄 그어지는데 jstl 설치 안해서 그럼. lib에 넣어준다. jsp 확장태그로 c로 쓴다.-->


<!DOCTYPE html>
<html lang="en">
    <head>
    <!-- 부트스트랩 출처 : https://startbootstrap.com/template/blog-home -->
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
        <meta name="description" content="" />
        <meta name="author" content="" />
        <title>${postObj.POST_TITLE}</title>
        <!-- Favicon-->
       <!-- <link rel="icon" type="image/x-icon" href="assets/favicon.ico" /> -->
        <!-- Core theme CSS (includes Bootstrap)-->
         <link href="../resources/css/mainPage.css" rel="stylesheet" />
    </head>
    <body>
        <!-- Responsive navbar-->
        <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
            <div class="container">
                <a class="navbar-brand" href="#!">N Blog</a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button>
                <div class="collapse navbar-collapse" id="navbarSupportedContent">
                    <ul class="navbar-nav ms-auto mb-2 mb-lg-0">
                        <li class="nav-item"><a class="nav-link" href="#">Home</a></li>
                        <li class="nav-item"><a class="nav-link" href="#!">About</a></li>
                        <li class="nav-item"><a class="nav-link" href="#!">Contact</a></li>
                        <li class="nav-item"><a class="nav-link active" aria-current="page" href="#">Blog</a></li>
                    </ul>
                </div>
            </div>
        </nav>
        <!-- Page content-->
        <div class="container mt-5">
            <div class="row">
                <div class="col-lg-8">
                    <!-- Post content-->
                    <article>
                        <!-- Post header-->
                        <header class="mb-4">
                            <!-- Post title-->
                            <h1 class="fw-bolder mb-1">${postObj.POST_TITLE}</h1>
                            <!-- Post meta content-->
                            <div class="text-muted fst-italic mb-2">${postObj.POST_UPLOADTIME} by ${postObj.MEM_NO}</div>
                            <!-- Post categories-->
                            <a class="badge bg-secondary text-decoration-none link-light" href="#!">Web Design</a>
                            <a class="badge bg-secondary text-decoration-none link-light" href="#!">Freebies</a>
                        </header>
                        <!-- Preview image figure-->
                        <figure class="mb-4"><img class="img-fluid rounded" src="../resources/img/thumbnail/${postObj.POST_THUMBNAIL}" alt="..." /></figure>
                        <!-- Post content-->
                        <section class="mb-5">
                            <p class="fs-5 mb-4">${postObj.POST_CONTENT}</p>
                            <p class="fs-5 mb-4">The universe is large and old, and the ingredients for life as we know it are everywhere, so there's no reason to think that Earth would be unique in that regard. Whether of not the life became intelligent is a different question, and we'll see if we find that.</p>
                            <p class="fs-5 mb-4">If you get asteroids about a kilometer in size, those are large enough and carry enough energy into our system to disrupt transportation, communication, the food chains, and that can be a really bad day on Earth.</p>
                            <h2 class="fw-bolder mb-4 mt-5">I have odd cosmic thoughts every day</h2>
                            <p class="fs-5 mb-4">For me, the most fascinating interface is Twitter. I have odd cosmic thoughts every day and I realized I could hold them to myself or share them with people who might be interested.</p>
                            <p class="fs-5 mb-4">Venus has a runaway greenhouse effect. I kind of want to know what happened there because we're twirling knobs here on Earth without knowing the consequences of it. Mars once had running water. It's bone dry today. Something bad happened there as well.</p>
                        </section>
                    </article>
                    <!-- Comments section-->
                    <section class="mb-5">
                        <div class="card bg-light">
                            <div class="card-body">
                                <!-- Comment form-->
                                <form class="mb-4"><textarea class="form-control" rows="3" placeholder="Join the discussion and leave a comment!"></textarea></form>
                                <!-- Comment with nested comments-->
                                <div class="d-flex mb-4">
                                    <!-- Parent comment-->
                                    <div class="flex-shrink-0"><img class="rounded-circle" src="https://dummyimage.com/50x50/ced4da/6c757d.jpg" alt="..." /></div>
                                    <div class="ms-3">
                                        <div class="fw-bold">Commenter Name</div>
                                        If you're going to lead a space frontier, it has to be government; it'll never be private enterprise. Because the space frontier is dangerous, and it's expensive, and it has unquantified risks.
                                        <!-- Child comment 1-->
                                        <div class="d-flex mt-4">
                                            <div class="flex-shrink-0"><img class="rounded-circle" src="https://dummyimage.com/50x50/ced4da/6c757d.jpg" alt="..." /></div>
                                            <div class="ms-3">
                                                <div class="fw-bold">Commenter Name</div>
                                                And under those conditions, you cannot establish a capital-market evaluation of that enterprise. You can't get investors.
                                            </div>
                                        </div>
                                        <!-- Child comment 2-->
                                        <div class="d-flex mt-4">
                                            <div class="flex-shrink-0"><img class="rounded-circle" src="https://dummyimage.com/50x50/ced4da/6c757d.jpg" alt="..." /></div>
                                            <div class="ms-3">
                                                <div class="fw-bold">Commenter Name</div>
                                                When you put money directly to a problem, it makes a good headline.
                                            </div>
                                        </div>
                                    </div>
                                </div>
                                <!-- Single comment-->
                                <div class="d-flex">
                                    <div class="flex-shrink-0"><img class="rounded-circle" src="https://dummyimage.com/50x50/ced4da/6c757d.jpg" alt="..." /></div>
                                    <div class="ms-3">
                                        <div class="fw-bold">Commenter Name</div>
                                        When I look at the universe and all the ways the universe wants to kill us, I find it hard to reconcile that with statements of beneficence.
                                    </div>
                                </div>
                            </div>
                        </div>
                    </section>
                </div>
                <!-- Side widgets-->
                <div class="col-lg-4">
                    <!-- Search widget-->
                    <div class="card mb-4">
                        <div class="card-header">Search</div>
                        <div class="card-body">
                            <div class="input-group">
                                <input class="form-control" type="text" placeholder="Enter search term..." aria-label="Enter search term..." aria-describedby="button-search" />
                                <button class="btn btn-primary" id="button-search" type="button">Go!</button>
                            </div>
                        </div>
                    </div>
                    <!-- Categories widget-->
                    <div class="card mb-4">
                        <div class="card-header">Categories</div>
                        <div class="card-body">
                            <div class="row">
                                <div class="col-sm-6">
                                    <ul class="list-unstyled mb-0">
                                        <li><a href="#!">Web Design</a></li>
                                        <li><a href="#!">HTML</a></li>
                                        <li><a href="#!">Freebies</a></li>
                                    </ul>
                                </div>
                                <div class="col-sm-6">
                                    <ul class="list-unstyled mb-0">
                                        <li><a href="#!">JavaScript</a></li>
                                        <li><a href="#!">CSS</a></li>
                                        <li><a href="#!">Tutorials</a></li>
                                    </ul>
                                </div>
                            </div>
                        </div>
                    </div>
                    <!-- Side widget-->
                    <div class="card mb-4">
                        <div class="card-header">Side Widget</div>
                        <div class="card-body">You can put anything you want inside of these side widgets. They are easy to use, and feature the Bootstrap 5 card component!</div>
                    </div>
                </div>
            </div>
        </div>
        <!-- Footer-->
        <footer class="py-5 bg-dark">
            <div class="container"><p class="m-0 text-center text-white">Copyright &copy; Your Website 2022</p></div>
        </footer>
        <!-- Bootstrap core JS-->
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
        <!-- Core theme JS-->
     <!--   <script src="js/scripts.js"></script>-->
    </body>
</html>

 

2) Controller

//-------------------- 게시글 상세보기 postViewAction 페이지 생성 ---------------------------				
else if (command.equals("/member/postViewAction.me")) {
    action = new PostViewAction();
    // PostViewAction, 게시글 상세보기 페이지 표시.
    try {
        forward = action.execute(req, resp);
        System.out.println( "게시글 상세보기 표시"))
    }
    catch (Exception e) {
        e.printStackTrace();
    }
}

3) Action

그냥 게시글이기 때문에 session은 필요없다. 관리자가 못볼 글들은 아니기 때문에... 파라미터 값을 받아서 서비스로 넘겨준다.

package action;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import svc.PostViewService;
import vo.ActionForward;
import vo.PostBean;

/* 인터페이스를 함께 추가했다. 게시글 상세보기 표시를 위한 클래스이다.*/
public class PostViewAction implements Action {

	@Override
	public ActionForward execute(HttpServletRequest req, HttpServletResponse resp) throws Exception {
		ActionForward forward = null; //어디로 갈지?
			forward = new ActionForward();

			int ClickPostNo = Integer.parseInt(req.getParameter("postno"));
			//URL 주소 뒤에 붙은 쿼리스트링은 getParameter로 가져온다.
			System.out.println("클릭한 포스트 번호: "+ClickPostNo);
	
			PostViewService postViewService = new PostViewService();
			//서비스 svc에서 getPostinfo를 만들어준다. 게시글 번호 보내주자.
			PostBean postObj = postViewService.getPostinfo(ClickPostNo);
			req.setAttribute("postObj", postObj);
			forward.setPath("./blogPost.jsp");

		return forward;
	}
}

4) Service

DB연결해서 커넥션 풀 열고 닫기

package svc;
import static db.JdbcUtil.close;
import static db.JdbcUtil.getConnection;
import java.sql.Connection;
import DAO.PostDAO;
import vo.PostBean;


/* 게시글 상세페이지를 띄울 시 DB와 JSP를 연결해주는 역할을 담당하는 클래스(Service) */
public class PostViewService {
	
	public PostBean getPostinfo(int postNo) {
		PostDAO postDAO = PostDAO.getInstance();
		// 객체에 저장된 고객 정보를 DB로 전달하는 클래스.
		// DAO : Data Access Object
		Connection con = getConnection();
		// DB와 jsp간의 연결을 해주는 것을 담당함.
		postDAO.setConnection(con);
		
		PostBean post = postDAO.selectPost(postNo); //selectPost는 postDAO에서 받아온다.

		close(con); //커넥션풀 닫음

		return post;
	}
}

5) DAO

postDAO에 게시글 목록을 뽑아주는 selectPostList가 이미 있다. 그 아래에 새롭게 selectPost를 생성해준다.

//=========================== 게시글 상세보기를 가져와 보여주는 SQL로직 ===============================
// PostViewService에서 게시글 상세보기정보를 볼때 DB와 JSP를 연결할 때 인자로 쓰임.
public PostBean selectPost(int postNo) {
    String sql = "select * from post_info where POST_NO=?""
    PostBean pb=null;

    try {
        pstmt=con.prepareStatement(sql);
        pstmt.setInt(1,postNo);
        rs=pstmt.executeQuery();

        if(rs.next()) {

            pb=new PostBean();

            //게시글 1개의 정보를 저장할 수 있는 PostBean 객체 생성.
            pb.setPOST_NO(rs.getInt( "POST_NO"))) //게시글번호
            pb.setMEM_NO(rs.getInt( "MEM_NO"))) //회원번호
            pb.setPOST_TITLE(rs.getString( "POST_TITLE"))) //게시글제목
            pb.setPOST_THUMBNAIL(rs.getString( "POST_THUMBNAIL"))) //게시글섬네일
            pb.setPOST_VIDEO(rs.getString( "POST_VIDEO"))) //게시글비디오
            pb.setPOST_CONTENT(rs.getString( "POST_CONTENT"))) //게시글 내용
            pb.setVisit_cnt(rs.getInt( "Visit_cnt"))) //게시글조회수
            pb.setPOST_UPLOADTIME(rs.getString( "POST_UPLOADTIME"))) //게시글업로드타임
        }
    }
    catch(Exception ex) {
        System.out.println( "getSelectPost 에러: " + ex))

    }
    finally {
        close(rs);
        close(pstmt);
    }
    return pb;
}

6) VO

vo에 있는 PostBean의 정보는 이전글 참고.

서버콘솔에서 포스트 번호 확인

3. 해당 상세페이지에 글쓴이의 정보가 위젯에 뜨게 하기

사실 연결은 게시글 목록 리스트 띄우는 것과 똑같이 부트스트랩 폼에 필요한 정보를 넣어주면 된다.

네비게이션을 따로 분리해서 띄워주는 방식을 사용할까 하다가, 결국 어떤 블로그 게시글을 클릭하면 그 글을 쓴 사람의 블로그로 이동된다는 사실을 깨닫고 이를 위젯으로 표시해주기로 했다.

글쓴이의 id값, 그리고 mem_pic으로 설정해준 멤버의 프로필 사진을 위젯에 띄워주자.

1) 위젯을 왼쪽으로 옮겨서 프로필 사진 나올 수 있게 하기

간단하게 div 위치를 수정해서 프로필이 나올 수 있게 한다. 메인페이지에 오른쪽에 위젯이 있다보니 차별점을 두고 싶었음.

게시글 출력되는 곳이 <div class="col-lg-8">로 되어있는데, 이를 <div class="col-lg-9">로 늘려주고 위젯 영역을 3으로 설정해주었다. 위젯이 그닥 클 필요는 없어보여서...

위젯에 사진 보이는 것은 기존 css를 이용해서 게시글 미리보기 이미지를 출력하는 class="card-img-top"를 사용했다.

▼blogPost.jsp 에 블로그 헤더 및 위젯 왼쪽으로 추가

    <!-- 블로그 헤더 및 블로그 배경화면 -->
        <header class="py-5 bg-light border-bottom mb-4" style="background-image: url('https://www.urbanbrush.net/web/wp-content/uploads/edd/2022/01/urbanbrush-20220105101328484351.jpg')">
            <div class="container">
                <div class="text-center my-5">
                    <h1 class="fw-bolder"> ${postObj.MEM_NO} 회원님의 블로그입니다.</h1>
                    <p class="lead mb-0">내가 쓴 글을 볼 수 있습니다.</p>
                </div>
            </div>
        </header>
        
        <!-- Page content-->
        <div class="container mt-5">
            <div class="row">
            
            <!-- 왼쪽 블로그 소개 위젯-->
                <div class="col-lg-3">
                <!-- Side widget-->
                    <div class="card mb-4">
                    	<img class="card-img-top" src="../resources/img/blog/${postObj.MEM_PIC}" alt="..." />
                        <div class="card-header">${postObj.MEM_ID}</div>
                        <div class="card-body">블로그 소개글 쓰는 곳.맛집을 좋아하는 ㅇㅇ의 블로그입니다!</div>
                    </div>
                    <!-- Categories widget-->
                    <div class="card mb-4">
                        <div class="card-header">카테고리?</div>
                        <div class="card-body">
                            <div class="row">
                                <div class="col-sm-6">
                                    <ul class="list-unstyled mb-0">
                                        <li><a href="postEdit.jsp">내 블로그</a></li>
                                        <li><a href="#">오늘 ??명 방문 ></a></li>
                                        <li></li>
                                    </ul>
                                </div>
                                <div class="col-sm-6">
                                    <ul class="list-unstyled mb-0">
                                        <li><a href="postEdit.jsp">새글작성</a></li>
                                        <li><a href="#">로그아웃</a></li>
                                    </ul>
                                </div>
                            </div>
                        </div>
                    </div>
                    
                    <!-- Search widget-->
                    <div class="card mb-4">
                        <div class="card-header">내 블로그에서 검색</div>
                        <div class="card-body">
                            <div class="input-group">
                                <input class="form-control" type="text" placeholder="검색어를 입력하세요" aria-label="Enter search term..." aria-describedby="button-search" />
                                <button class="btn btn-primary" id="button-search" type="button">검색</button>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="col-lg-9">
                    <!-- Post content-->
                    <article>
                        <!-- Post header-->
                        <header class="mb-4">
                            <!-- Post title-->
                            <h1 class="fw-bolder mb-1">${postObj.POST_TITLE}</h1>
                            
                            
                           ...

2) 위젯과 블로그 배경에 멤버 정보 띄우기

기존에 게시글 상세보기 페이지의 SQL 문은 아래와 같다.

select * from post_info where POST_NO=?

그냥 게시글 번호가 맞는 포스트 정보를 전체 가져와서 사용했음.

post_info 테이블에서 FK로 mem_no를 연결해놓았기 때문에 멤버정보도 가지고 올 수 있음. join을 사용한다.

select p.*,m.mem_id,m.mem_pic from post_info p 
    left join memberinfo m on p.mem_no = m.mem_no where POST_NO=?

join으로 mem_no가 서로 같다는걸 표시해주고 그 중 post_no가 같은 게시글의 정보를 가져온다.

단, 게시글 정보 및 멤버id와 사진도 함께 가져옴. -> 일단 이것만 필요하니까.

VO에서 post_info 테이블 정보를 담아 가져오는데, 어차피 DAO에서 하나에 담아서 리턴쳐주기 때문에 VO에 변수를 하나 늘려주었다.

▼  PostBean.java에 내용 추가

   ...
    
	//블로그 게시글 네비에 표시하기 위해 추가
	private String MEM_ID;
	private String MEM_PIC;

	
	public String getMEM_ID() {
		return MEM_ID;
	}
	public void setMEM_id(String mEM_ID) {
		MEM_ID = mEM_ID;
	}
	public String getMEM_PIC() {
		return MEM_PIC;
	}
	public void setMEM_PIC(String mEM_PIC) {
		MEM_PIC = mEM_PIC;
	}
	
	...

▼  PostDAO.java에 회원정보 받아줌

...
pb.setPOST_UPLOADTIME(rs.getString("POST_UPLOADTIME")); //게시글업로드타임
				

// 가져올 회원 정보 추가
pb.setMEM_id(rs.getString("MEM_ID")); //게시글 쓴 회원 id
pb.setMEM_PIC(rs.getString("MEM_PIC")); //게시글 쓴 회원 사진

...

		}
		return pb;
	}

어차피 return은 PostBean에서 가져온 pb 변수이기 때문에 함께 담아서 보내주었다.

그럼 jsp 페이지에서 다른 변수들과 같은 방법으로 띄울 수 있음.

 blogPost.jsp에 내용 띄워줌

<h1 class="fw-bolder"> ${postObj.MEM_NO} 회원님의 블로그입니다.</h1>

...

<div class="card mb-4">
   <img class="card-img-top" src="../resources/img/blog/${postObj.MEM_PIC}" alt="..." />
   <div class="card-header">${postObj.MEM_ID}</div>
   <div class="card-body">블로그 소개글 쓰는 곳.맛집을 좋아하는 ㅇㅇ의 블로그입니다! 이미지는 일단 고정으로 박아뒀지롱...</div>
</div>

결과창

▼blogPost.jsp 전체

더보기
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ page import="vo.MemberBean"%>
<%@ page import="java.util.*"%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!-- 빨간줄 그어지는데 jstl 설치 안해서 그럼. lib에 넣어준다. jsp 확장태그로 c로 쓴다.-->


<!DOCTYPE html>
<html lang="en">
    <head>
    <!-- 부트스트랩 출처 : https://startbootstrap.com/template/blog-home -->
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
        <meta name="description" content="" />
        <meta name="author" content="" />
        <title>${postObj.POST_TITLE}</title>
        <!-- Favicon-->
       <!-- <link rel="icon" type="image/x-icon" href="assets/favicon.ico" /> -->
        <!-- Core theme CSS (includes Bootstrap)-->
         <link href="../resources/css/mainPage.css" rel="stylesheet" />
    </head>
    <body>
        <!-- Responsive navbar-->
        <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
            <div class="container">
                <a class="navbar-brand" href="#!">N Blog</a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button>
                <div class="collapse navbar-collapse" id="navbarSupportedContent">
                    <ul class="navbar-nav ms-auto mb-2 mb-lg-0">
                        <li class="nav-item"><a class="nav-link" href="#">Home</a></li>
                        <li class="nav-item"><a class="nav-link" href="#!">About</a></li>
                        <li class="nav-item"><a class="nav-link" href="#!">Contact</a></li>
                        <li class="nav-item"><a class="nav-link active" aria-current="page" href="#">Blog</a></li>
                    </ul>
                </div>
            </div>
        </nav>
        
                <!-- 블로그 헤더 및 블로그 배경화면 -->
        <header class="py-5 bg-light border-bottom mb-4" style="background-image: url('https://www.urbanbrush.net/web/wp-content/uploads/edd/2022/01/urbanbrush-20220105101328484351.jpg')">
            <div class="container">
                <div class="text-center my-5">
                    <h1 class="fw-bolder"> ${postObj.MEM_NO} 회원님의 블로그입니다.</h1>
                    <p class="lead mb-0">내가 쓴 글을 볼 수 있습니다.</p>
                </div>
            </div>
        </header>
        
        <!-- Page content-->
        <div class="container mt-5">
            <div class="row">
            
            <!-- 왼쪽 블로그 소개 위젯-->
                <div class="col-lg-3">
                <!-- Side widget-->
                    <div class="card mb-4">
                    	<img class="card-img-top" src="../resources/img/blog/${postObj.MEM_PIC}" alt="..." />
                        <div class="card-header">${postObj.MEM_ID}</div>
                        <div class="card-body">블로그 소개글 쓰는 곳.맛집을 좋아하는 ㅇㅇ의 블로그입니다! 이미지는 일단 고정으로 박아뒀지롱...</div>
                    </div>
                    <!-- Categories widget-->
                    <div class="card mb-4">
                        <div class="card-header">카테고리?</div>
                        <div class="card-body">
                            <div class="row">
                                <div class="col-sm-6">
                                    <ul class="list-unstyled mb-0">
                                        <li><a href="postEdit.jsp">내 블로그</a></li>
                                        <li><a href="#">오늘 ??명 방문 ></a></li>
                                        <li></li>
                                    </ul>
                                </div>
                                <div class="col-sm-6">
                                    <ul class="list-unstyled mb-0">
                                        <li><a href="postEdit.jsp">새글작성</a></li>
                                        <li><a href="#">로그아웃</a></li>
                                    </ul>
                                </div>
                            </div>
                        </div>
                    </div>
                    
                    <!-- Search widget-->
                    <div class="card mb-4">
                        <div class="card-header">내 블로그에서 검색</div>
                        <div class="card-body">
                            <div class="input-group">
                                <input class="form-control" type="text" placeholder="검색어를 입력하세요" aria-label="Enter search term..." aria-describedby="button-search" />
                                <button class="btn btn-primary" id="button-search" type="button">검색</button>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="col-lg-9">
                    <!-- Post content-->
                    <article>
                        <!-- Post header-->
                        <header class="mb-4">
                            <!-- Post title-->
                            <h1 class="fw-bolder mb-1">${postObj.POST_TITLE}</h1>
                            <!-- Post meta content-->
                            <div class="text-muted fst-italic mb-2">${postObj.POST_UPLOADTIME} by ${postObj.MEM_ID}</div>
                            <!-- Post categories-->
                            <a class="badge bg-secondary text-decoration-none link-light" href="#!">Web Design</a>
                            <a class="badge bg-secondary text-decoration-none link-light" href="#!">Freebies</a>
                        </header>
                        <!-- Preview image figure-->
                        <figure class="mb-4"><img class="img-fluid rounded" src="../resources/img/thumbnail/${postObj.POST_THUMBNAIL}" alt="..." /></figure>
                        <!-- Post content-->
                        <section class="mb-5">
                            <p class="fs-5 mb-4">${postObj.POST_CONTENT}</p>
                            <p class="fs-5 mb-4">The universe is large and old, and the ingredients for life as we know it are everywhere, so there's no reason to think that Earth would be unique in that regard. Whether of not the life became intelligent is a different question, and we'll see if we find that.</p>
                            <p class="fs-5 mb-4">If you get asteroids about a kilometer in size, those are large enough and carry enough energy into our system to disrupt transportation, communication, the food chains, and that can be a really bad day on Earth.</p>
                            <h2 class="fw-bolder mb-4 mt-5">I have odd cosmic thoughts every day</h2>
                            <p class="fs-5 mb-4">For me, the most fascinating interface is Twitter. I have odd cosmic thoughts every day and I realized I could hold them to myself or share them with people who might be interested.</p>
                            <p class="fs-5 mb-4">Venus has a runaway greenhouse effect. I kind of want to know what happened there because we're twirling knobs here on Earth without knowing the consequences of it. Mars once had running water. It's bone dry today. Something bad happened there as well.</p>
                        </section>
                    </article>
                    <!-- Comments section-->
                    <section class="mb-5">
                        <div class="card bg-light">
                            <div class="card-body">
                                <!-- Comment form-->
                                <form class="mb-4"><textarea class="form-control" rows="3" placeholder="Join the discussion and leave a comment!"></textarea></form>
                                <!-- Comment with nested comments-->
                                <div class="d-flex mb-4">
                                    <!-- Parent comment-->
                                    <div class="flex-shrink-0"><img class="rounded-circle" src="https://dummyimage.com/50x50/ced4da/6c757d.jpg" alt="..." /></div>
                                    <div class="ms-3">
                                        <div class="fw-bold">Commenter Name</div>
                                        If you're going to lead a space frontier, it has to be government; it'll never be private enterprise. Because the space frontier is dangerous, and it's expensive, and it has unquantified risks.
                                        <!-- Child comment 1-->
                                        <div class="d-flex mt-4">
                                            <div class="flex-shrink-0"><img class="rounded-circle" src="https://dummyimage.com/50x50/ced4da/6c757d.jpg" alt="..." /></div>
                                            <div class="ms-3">
                                                <div class="fw-bold">Commenter Name</div>
                                                And under those conditions, you cannot establish a capital-market evaluation of that enterprise. You can't get investors.
                                            </div>
                                        </div>
                                        <!-- Child comment 2-->
                                        <div class="d-flex mt-4">
                                            <div class="flex-shrink-0"><img class="rounded-circle" src="https://dummyimage.com/50x50/ced4da/6c757d.jpg" alt="..." /></div>
                                            <div class="ms-3">
                                                <div class="fw-bold">Commenter Name</div>
                                                When you put money directly to a problem, it makes a good headline.
                                            </div>
                                        </div>
                                    </div>
                                </div>
                                <!-- Single comment-->
                                <div class="d-flex">
                                    <div class="flex-shrink-0"><img class="rounded-circle" src="https://dummyimage.com/50x50/ced4da/6c757d.jpg" alt="..." /></div>
                                    <div class="ms-3">
                                        <div class="fw-bold">Commenter Name</div>
                                        When I look at the universe and all the ways the universe wants to kill us, I find it hard to reconcile that with statements of beneficence.
                                    </div>
                                </div>
                            </div>
                        </div>
                    </section>
                </div>
                <!-- Side widgets-->
                <div class="col-lg-4">
                    <!-- Search widget-->
                    <div class="card mb-4">
                        <div class="card-header">Search</div>
                        <div class="card-body">
                            <div class="input-group">
                                <input class="form-control" type="text" placeholder="Enter search term..." aria-label="Enter search term..." aria-describedby="button-search" />
                                <button class="btn btn-primary" id="button-search" type="button">Go!</button>
                            </div>
                        </div>
                    </div>
                    <!-- Categories widget-->
                    <div class="card mb-4">
                        <div class="card-header">Categories</div>
                        <div class="card-body">
                            <div class="row">
                                <div class="col-sm-6">
                                    <ul class="list-unstyled mb-0">
                                        <li><a href="#!">Web Design</a></li>
                                        <li><a href="#!">HTML</a></li>
                                        <li><a href="#!">Freebies</a></li>
                                    </ul>
                                </div>
                                <div class="col-sm-6">
                                    <ul class="list-unstyled mb-0">
                                        <li><a href="#!">JavaScript</a></li>
                                        <li><a href="#!">CSS</a></li>
                                        <li><a href="#!">Tutorials</a></li>
                                    </ul>
                                </div>
                            </div>
                        </div>
                    </div>
                    <!-- Side widget-->
                    <div class="card mb-4">
                        <div class="card-header">Side Widget</div>
                        <div class="card-body">You can put anything you want inside of these side widgets. They are easy to use, and feature the Bootstrap 5 card component!</div>
                    </div>
                </div>
            </div>
        </div>
        <!-- Footer-->
        <footer class="py-5 bg-dark">
            <div class="container"><p class="m-0 text-center text-white">Copyright &copy; Your Website 2022</p></div>
        </footer>
        <!-- Bootstrap core JS-->
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
        <!-- Core theme JS-->
     <!--   <script src="js/scripts.js"></script>-->
    </body>
</html>

 

 

 

 

▼ 진행중인 gitHub 링크를 남깁니다.

 

GitHub - k-haein/BlogPrj: 회원가입/로그인/게시글 등록 등의 블로그를 처음부터 만듭니다.

회원가입/로그인/게시글 등록 등의 블로그를 처음부터 만듭니다. Contribute to k-haein/BlogPrj development by creating an account on GitHub.

github.com

 

반응형
LIST
반응형
SMALL

아이디 중복검사 기능 구현

ajax 통신을 통해 DB에 Id가 중복되는지 확인해서 id중복검사 기능을 구현해보겠다.

[ 만들 기능 ]
1. 빈값 체크
2. 유효성 검사(5~20자 특수문자 -와_만 허용)
3. id 중복체크

1. jsp에서 ajax 통신을 이용해서 호출 URL로 Data 보냄
2. Controller에서 호출 URL을 받아 Action과 연결함.
3. Action에서 getParameter로 가져온 userId값을 Service로 보내줌.
4. Service에서 id값을 받아 커넥션 풀에서 DB 연결하고, 회원정보 찾아봄.
5. DAO 안에서 service에게 전달받은 id를 SQL에 맞게 검색 후 결과값 리턴해서 Service에게 줌. (+커넥션 끊음)
6. 다시 Service에서 DB 명령 완료하고 결과 리턴해줌.(중복이면 0, 안중복이면 1)
7. Action에서 Service에게 받은 결과값을 getWriter().write로 응답하는 스트림에 결과값 던져줌.
8. 다시 ajax 성공 결과값인 success로 와서 응답받은 result값에 따라 메시지 출력해줌.


1. joinUs.jsp - id를 입력하는 input 확인

<form name="joinform" action="./memberJoinAction.me" method="post">
   <!-- container -->
    <div id="container" role="main">
        <div id="content">
            <!-- tg-text=title -->
            <h2 class="blind">네이버 회원가입</h2>
            <div class="join_content">
                <!-- 아이디, 비밀번호 입력 -->
                <div class="row_group">
                    <div class="join_row">
                        <h3 class="join_title"><label for="id">아이디</label></h3>
                        <span class="ps_box int_id">
                        	<!-- ID 입력 및 포커스아웃되면 중복검사 -->
                            <input type="text" id="id" name="MEMBER_ID" class="int" title="ID" maxlength="20">
                            <span class="step_url">@naver.com</span></span>
                        <span class="error_next_box" id="idMsg" style="display:none" aria-live="assertive"></span>
                    </div>

2. joinUs.jsp - 하단 script에서 IdCheck 함수 시행

▼ id 체크하는 jsp 파일 script 전체

더보기
//필요한 플래그들을 세운다.
var idFlag = false;

...

//id = 'id' 인 input에 플래그가 있다. checkId 함수를 시행한다.
$("#id").blur(function() {
    idFlag = false;
    checkId("first");
});

//========= id 체크 함수 시행 ===============
function checkId(event) {
    if (idFlag) 
        return true;
    
    var id = $("#id").val();
    var oMsg = $("#idMsg");
    var oInput = $("#id");
	
  	//============ [id 필수입력] ============
    if (id == "") {
        showErrorMsg(oMsg, "필수 정보입니다."); //에러메시지 출력
        setFocusToInputObject(oInput); //전송 플래그 조절
        return false;
    }
	
  //============ [id 유효성검사] ============
    var isID = /^[a-z0-9][a-z0-9_\-]{4,19}$/;
    if (!isID.test(id)) {
        showErrorMsg(oMsg, "5~20자의 영문 소문자, 숫자와 특수기호(_),(-)만 사용 가능합니다."); //에러메시지 출력
        setFocusToInputObject(oInput); //전송 플래그 조절
        return false;
    }

  //============ [id 중복체크] ============
    // 추가) ajax 통신으로 DB 데이터 조회해서 중복확인하기!!!(post 방식) 
    idFlag = false; //검사 flag
    let userId = $('input[name=MEMBER_ID]').val(); // input_id에 입력되는 값
    console.log("입력한 id값 : " + userId);
	
  	//id 중복체크를 위해 input에 입력한 id값을 가져와서 ajax data로 반드시 보내줘야한다.
    $.ajax({
        url: "IdCheckService/idcheckAjax",
        type: "post",
        data: {
            userId: userId 
        },
        dataType: 'json',
        success: function (result) {
            //Action에서 받은 result값 : 1이면 사용가능, 0이면 중복됨
            if (result == 0) { //돌려받은 결과가 중복이 존재한다는 0이면

                showErrorMsg(oMsg, "이미 사용중이거나 탈퇴한 아이디입니다."); //에러메시지 출력
                setFocusToInputObject(oInput); //전송 플래그 조절

            } else { //돌려받은 결과가 중복이 없다는 false이면

                if (event == "first") { //그리고 #id라면
                    showSuccessMsg(oMsg, "멋진 아이디네요!"); //에러메시지 출력
                } else {
                    hideMsg(oMsg); //메시지 숨김
                }
                idFlag = true; //id 플래그 1로 변경. 더이상 id alert 발생 x
            }
        },
        error: function () {
            alert("서버요청실패");
        }
    })
    return true;
} //function checkId(event) { 끝

1) id 필수입력

  	//============ [id 필수입력] ============
    if (id == "") {
        showErrorMsg(oMsg, "필수 정보입니다."); //에러메시지 출력
        setFocusToInputObject(oInput); //전송 플래그 조절
        return false;
    }

2) id 유효성 검사 - 정규식 활용

  //============ [id 유효성검사] ============
    var isID = /^[a-z0-9][a-z0-9_\-]{4,19}$/;
    if (!isID.test(id)) {
        showErrorMsg(oMsg, "5~20자의 영문 소문자, 숫자와 특수기호(_),(-)만 사용 가능합니다."); //에러메시지 출력
        setFocusToInputObject(oInput); //전송 플래그 조절
        return false;
    }

3) id 중복체크 - ajax 통신을 통해 결과값 받음.

  //============ [id 중복체크] ============
      // 추가) ajax 통신으로 DB 데이터 조회해서 중복확인하기!!!(post 방식) 
    idFlag = false; //검사 flag
    let userId = $('input[name=MEMBER_ID]').val(); // input_id에 입력되는 값
    console.log("입력한 id값 : " + userId);
	
  	//id 중복체크를 위해 input에 입력한 id값을 가져와서 ajax data로 반드시 보내줘야한다.
    $.ajax({
        url: "IdCheckService/idcheckAjax",
        type: "post",
        data: {
            userId: userId 
        },
        dataType: 'json',
        success: function (result) {
            //Action에서 받은 result값 : 1이면 사용가능, 0이면 중복됨
            if (result == 0) { //돌려받은 결과가 중복이 존재한다는 0이면

                showErrorMsg(oMsg, "이미 사용중이거나 탈퇴한 아이디입니다."); //에러메시지 출력
                setFocusToInputObject(oInput); //전송 플래그 조절

            } else { //돌려받은 결과가 중복이 없다는 false이면

                if (event == "first") { //그리고 #id라면
                    showSuccessMsg(oMsg, "멋진 아이디네요!"); //에러메시지 출력
                } else {
                    hideMsg(oMsg); //메시지 숨김
                }
                idFlag = true; //id 플래그 1로 변경. 더이상 id alert 발생 x
            }
        },
        error: function () {
            alert("서버요청실패");
        }
    })
    return true;

3.  MemberDAO.java - SQL 조회 중복여부 확인

...

	public static MemberDAO instance;
	Connection con;
	// jsp와 오라클 연결 유지.
	PreparedStatement pstmt;
	// 쿼리문 전달.
	ResultSet rs;
	// 쿼리문 결과 저장 객체.
	DataSource ds;
	// 어떤 DB에 연결할 지.
    
...
    
    //=========================== id 중복확인하는 SQL로직 ===============================
	public int checkId(String id) {  // 유저가 입력한 값을 매개변수로 한다
		/*
		회원가입 JSP에서 받아온 ID값과 기존 테이블의 ID값이 일치하는지 여부를
		확인하기 위해 Select명령어를 사용해 DAO를 만들었습니다. 
		Select값이 없는 경우 0을 출력하고,
		Select값이 있는 경우 1을 출력하도록 설정했습니다.*/
		String sql = "select * from memberinfo where MEM_ID=?"; // 입력값이 테이블에 있는지 확인
		
		int idCheck = 0;
	    try {
	    	pstmt = con.prepareStatement(sql); // pstmt에 위의 query 저장 후 DB에 연결 준비
	    	pstmt.setString(1, id); //첫번째 ?에 id 변수 셋팅
			System.out.println("DAO 내부) DB에 검색하는 id값(input에 쓴 값) : "+ id);
			
			rs = pstmt.executeQuery(); // query 실행 후 그 결과값을 rs에 저장
					
			
			if(rs.next() || id.equals("")) {
				idCheck = 0;  // 이미 존재하는 경우, 생성 불가능
				System.out.println("DAO 내부) id 중복됨");
			} else {
				idCheck = 1;  // 존재하지 않는 경우, 생성 가능
				System.out.println("DAO 내부) id 안중복");
			}
			
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			close(rs);
			close(pstmt);//마지막으로 연결 다 끊어주자. conn은 위에서 다 함.
		}

		return idCheck;
	}

4. MemberJoinService.java - DB와 jsp를 연결

	//=================== 추가) id 중복체크 하는 service ==============================
	public int checkIdService(String id) {
		MemberDAO memberDAO = MemberDAO.getInstance();
		// 객체에 저장된 고객 정보를 DB로 전달하는 클래스.
		// DAO : Data Access Object
		Connection con = getConnection();
		// DB와 jsp간의 연결을 해주는 것을 담당함.
		memberDAO.setConnection(con);
		
		int chkId = memberDAO.checkId(id);  //checkId는 MemberDAO에서 받아온다.
		//1이면 사용가능, 0이면 중복됨
		// DB추가 쿼리를 메소드로 구현.
		System.out.println("Service 내부) chkId : "+chkId);
		
		
		//============ "commit"이 꼭꼭 필요하다. ============
		if (chkId == 0 || chkId == 1) { 
			commit(con); //DB 명령 완료 확인
			System.out.println("Service 내부) DB commit");
		} else {
			rollback(con);
			//만약 제대로 동작하지 않았다면
			//쿼리문에 대해서 취소(rollback)
		}

		//============ 없었더니 안중복되었다고만 리턴됨..그냥 닫기만 한거임. ============
		
		
		close(con);
		System.out.println("Service 내부) DB close");
		//연결되지 않았으면 연결 해제(DB와 Connection의 연결 해제)

		return chkId;
	}

5. MemberIdCheckAction.java - ajax로 받은 값 service에 넘겨주고 조회한 뒤 결과값 전송받음

/* 인터페이스를 함께 추가했다. 로그인 처리를 위한 클래스이다.*/
public class MemberIdCheckAction implements Action { // Action을 implements 해줌

	@Override
	public ActionForward execute(HttpServletRequest req, HttpServletResponse resp) throws Exception {
		MemberJoinService memberJoinService = new MemberJoinService();
		//=========================== id중복 처리 ================================

		String userId = req.getParameter("userId"); //ajax로 전달한 data 값 userId
		//1이면 사용가능, 0이면 중복됨
		System.out.println("Action 내부) 입력한 id : "+userId);
		PrintWriter out = resp.getWriter();			
		int idChk = memberJoinService.checkIdService(userId); 
		//DB 조회해서 중복체크 결과값 전송받음(checkId). 1이면 사용가능, 0이면 중복됨				
		
		// 성공여부 확인 : 개발자용(서버콘솔)
		if (idChk == 0) { //id 중복
			System.out.println("Action 내부) 이미 존재하는 아이디입니다.");
		} else if (idChk == 1) {
			System.out.println("Action 내부) 사용 가능한 아이디입니다.");
		}
				
		out.write(idChk + ""); // --> ajax 결과값인 result가 됨
				// --> String으로 값을 내보낼 수 있도록 + "" 를 해준다
				//ajax의 result값 : 1이면 사용가능, 0이면 중복됨
		
		return null;
		//ActionForward를 null로 리턴하면 이미 response에 응답을 끝냈다는 의미가 된다. 다른 어떤 페이지로도 이동하지 않는다.	
	}
}

6. 컨트롤러에서 ajax URL에 해당하는 Action페이지 연결

//-------------------- id중복검사하는 membercheckAction 페이지 생성 ---------------------------				
else if (command.equals("/member/IdCheckService/idcheckAjax")) {
    action = new MemberIdCheckAction();
    try {
        forward = action.execute(req, resp); //받은 action을 뜯어서 SQL로 보내준다.
        System.out.printf( "MemberFrontController : idCheck - SQL DB로 보내는 로직 실행 \n",req, resp))

    }
    catch (Exception e) {
        e.printStackTrace();
        System.out.println( "MemberFrontController : idCheck - SQL DB로 보내는 로직 실패(위에 에러)"))
    }
}

7. result 값을 리턴받은 ajax에서 success에 설정한 메시지(oMsg)를 띄운다.

결과물

틀릴 시 댓글 부탁드립니다.

 

jQuery 를 이용하여 id, class, name 의 input value 값 가져오기

jQuery 를 이용하여 id, class, name 의 input value 값 가져오기 1) id 값 기준으로 가져 오기 var valueById = $('#inputId').val(); # 은 아이디를 의미 2) class 값 기준으로 가져 오기 var valueByClass = $('.inputClass').val();

heannim-world.tistory.com

 

 

HttpServletRequest 과 HttpServletResponse 메소드 정리

HttpServletRequest 과 HttpServletResponse 메소드 정리 HttpServletRequest 를 간단하게 req라고 해봅시다. req를 사용하면, 값을 받아올 수가 있는데, 회원정보를 보냈다면 req 객체 안에 모든 데이터들이 들어가

heannim-world.tistory.com

 

 

protected void doProcess(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException { 뜯어보기

컨트롤러에서 요청/응답 처리하는 법 protected void doProcess(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException { 하나하나 뜯어보자면, protected : java 접근자, 같은 폴더(패키지)및 그 클

heannim-world.tistory.com

▼ 진행중인 gitHub 링크를 남깁니다.

 

GitHub - k-haein/BlogPrj: 회원가입/로그인/게시글 등록 등의 블로그를 처음부터 만듭니다.

회원가입/로그인/게시글 등록 등의 블로그를 처음부터 만듭니다. Contribute to k-haein/BlogPrj development by creating an account on GitHub.

github.com

 

반응형
LIST
반응형
SMALL

+ Recent posts

반응형
LIST